Browse Source

go.crypto/openpgp: Allow config. of s2k count for symmetric encrypt.

This patch allows the user to choose the strength of the
passphrase mangling during the process in which the key is
produced from the passphrase. It only affects symmetric
encryption.

Unmodified code that calls openpgp.SymmetricallyEncrypt() will
continue to get the now-default count of 65536. Otherwise, a
count in the range [1024, 65011712] may be configured. Illegal
values in and outside this range will silently be rounded up
to legal values within the said range.

The test to s2k.Serialize() has been modified to test a
variety of non-default counts with all the valid hashes and
ensure that the decoding component of s2k can parse and
decrypt the result.

Additional testing has been done with GPG to ensure that the
latter can parse and decrypt files encrypted/encoded with
various counts.

LGTM=agl
R=golang-codereviews, agl
CC=golang-codereviews
https://golang.org/cl/176080043
Brian Gitonga Marete 11 years ago
parent
commit
ca455997ca

+ 11 - 0
openpgp/packet/config.go

@@ -32,6 +32,17 @@ type Config struct {
 	DefaultCompressionAlgo CompressionAlgo
 	DefaultCompressionAlgo CompressionAlgo
 	// CompressionConfig configures the compression settings.
 	// CompressionConfig configures the compression settings.
 	CompressionConfig *CompressionConfig
 	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 {
 func (c *Config) Random() io.Reader {

+ 1 - 1
openpgp/packet/symmetric_key_encrypted.go

@@ -120,7 +120,7 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf
 	keyEncryptingKey := make([]byte, keySize)
 	keyEncryptingKey := make([]byte, keySize)
 	// s2k.Serialize salts and stretches the passphrase, and writes the
 	// s2k.Serialize salts and stretches the passphrase, and writes the
 	// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
 	// 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 {
 	if err != nil {
 		return
 		return
 	}
 	}

+ 58 - 3
openpgp/s2k/s2k.go

@@ -23,6 +23,17 @@ type Config struct {
 	// Hash is the default hash function to be used. If
 	// Hash is the default hash function to be used. If
 	// nil, SHA1 is used.
 	// nil, SHA1 is used.
 	Hash crypto.Hash
 	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 {
 func (c *Config) hash() crypto.Hash {
@@ -34,6 +45,49 @@ func (c *Config) hash() crypto.Hash {
 	return c.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
 // 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.
 // 4880, section 3.7.1.1) using the given hash and input passphrase.
 func Simple(out []byte, h hash.Hash, in []byte) {
 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 {
 		if err != nil {
 			return
 			return
 		}
 		}
-		count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
+		count := decodeCount(buf[8])
 		f := func(out, in []byte) {
 		f := func(out, in []byte) {
 			Iterated(out, h, in, buf[:8], count)
 			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 {
 	if _, err := io.ReadFull(rand, salt); err != nil {
 		return err
 		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 {
 	if _, err := w.Write(buf[:]); err != nil {
 		return err
 		return err
 	}
 	}

+ 4 - 1
openpgp/s2k/s2k_test.go

@@ -104,8 +104,11 @@ func TestParse(t *testing.T) {
 func TestSerialize(t *testing.T) {
 func TestSerialize(t *testing.T) {
 	hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160,
 	hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160,
 		crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224}
 		crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224}
+	testCounts := []int{-1, 0, 1024, 65536, 4063232, 65011712}
 	for _, h := range hashes {
 	for _, h := range hashes {
-		testSerializeConfig(t, &Config{Hash: h})
+		for _, c := range testCounts {
+			testSerializeConfig(t, &Config{Hash: h, S2KCount: c})
+		}
 	}
 	}
 }
 }