diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 8c4f213..3f17096 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -32,6 +32,17 @@ type Config struct { DefaultCompressionAlgo CompressionAlgo // CompressionConfig configures the compression settings. CompressionConfig *CompressionConfig + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int } func (c *Config) Random() io.Reader { diff --git a/openpgp/packet/symmetric_key_encrypted.go b/openpgp/packet/symmetric_key_encrypted.go index 23721bc..21739a1 100644 --- a/openpgp/packet/symmetric_key_encrypted.go +++ b/openpgp/packet/symmetric_key_encrypted.go @@ -120,7 +120,7 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf keyEncryptingKey := make([]byte, keySize) // s2k.Serialize salts and stretches the passphrase, and writes the // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. - err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash()}) + err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.S2KCount}) if err != nil { return } diff --git a/openpgp/s2k/s2k.go b/openpgp/s2k/s2k.go index 9e993ae..5416421 100644 --- a/openpgp/s2k/s2k.go +++ b/openpgp/s2k/s2k.go @@ -23,6 +23,17 @@ type Config struct { // Hash is the default hash function to be used. If // nil, SHA1 is used. Hash crypto.Hash + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int } func (c *Config) hash() crypto.Hash { @@ -34,6 +45,49 @@ func (c *Config) hash() crypto.Hash { return c.Hash } +func (c *Config) encodedCount() uint8 { + if c == nil || c.S2KCount == 0 { + return 96 // The common case. Correspoding to 65536 + } + + i := c.S2KCount + switch { + // Behave like GPG. Should we make 65536 the lowest value used? + case i < 1024: + i = 1024 + case i > 65011712: + i = 65011712 + } + + return encodeCount(i) +} + +// encodeCount converts an iterative "count" in the range 1024 to +// 65011712, inclusive, to an encoded count. The return value is the +// octet that is actually stored in the GPG file. encodeCount panics +// if i is not in the above range (encodedCount above takes care to +// pass i in the correct range). See RFC 4880 Section 3.7.7.1. +func encodeCount(i int) uint8 { + if i < 1024 || i > 65011712 { + panic("count arg i outside the required range") + } + + for encoded := 0; encoded < 256; encoded++ { + count := decodeCount(uint8(encoded)) + if count >= i { + return uint8(encoded) + } + } + + return 255 +} + +// decodeCount returns the s2k mode 3 iterative "count" corresponding to +// the encoded octet c. +func decodeCount(c uint8) int { + return (16 + int(c&15)) << (uint32(c>>4) + 6) +} + // Simple writes to out the result of computing the Simple S2K function (RFC // 4880, section 3.7.1.1) using the given hash and input passphrase. func Simple(out []byte, h hash.Hash, in []byte) { @@ -136,7 +190,7 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) { if err != nil { return } - count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6) + count := decodeCount(buf[8]) f := func(out, in []byte) { Iterated(out, h, in, buf[:8], count) } @@ -158,8 +212,9 @@ func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Co if _, err := io.ReadFull(rand, salt); err != nil { return err } - const count = 65536 // this is the default in gpg - buf[10] = 96 // 65536 iterations + encodedCount := c.encodedCount() + count := decodeCount(encodedCount) + buf[10] = encodedCount if _, err := w.Write(buf[:]); err != nil { return err } diff --git a/openpgp/s2k/s2k_test.go b/openpgp/s2k/s2k_test.go index 2e3d7b6..183d260 100644 --- a/openpgp/s2k/s2k_test.go +++ b/openpgp/s2k/s2k_test.go @@ -104,8 +104,11 @@ func TestParse(t *testing.T) { func TestSerialize(t *testing.T) { hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160, crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224} + testCounts := []int{-1, 0, 1024, 65536, 4063232, 65011712} for _, h := range hashes { - testSerializeConfig(t, &Config{Hash: h}) + for _, c := range testCounts { + testSerializeConfig(t, &Config{Hash: h, S2KCount: c}) + } } }