return g, nil
}
+// NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter
+// Mode, with randomly-generated nonces. The cipher must have been created by
+// [aes.NewCipher].
+//
+// It generates a random 96-bit nonce, which is prepended to the ciphertext by Seal,
+// and is extracted from the ciphertext by Open. The NonceSize of the AEAD is zero,
+// while the Overhead is 28 bytes (the combination of nonce size and tag size).
+//
+// A given key MUST NOT be used to encrypt more than 2^32 messages, to limit the
+// risk of a random nonce collision to negligible levels.
+func NewGCMWithRandomNonce(cipher Block) (AEAD, error) {
+ c, ok := cipher.(*aes.Block)
+ if !ok {
+ return nil, errors.New("cipher: NewGCMWithRandomNonce requires aes.Block")
+ }
+ g, err := gcm.New(c, gcmStandardNonceSize, gcmTagSize)
+ if err != nil {
+ return nil, err
+ }
+ return gcmWithRandomNonce{g}, nil
+}
+
+type gcmWithRandomNonce struct {
+ *gcm.GCM
+}
+
+func (g gcmWithRandomNonce) NonceSize() int {
+ return 0
+}
+
+func (g gcmWithRandomNonce) Overhead() int {
+ return gcmStandardNonceSize + gcmTagSize
+}
+
+func (g gcmWithRandomNonce) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if len(nonce) != 0 {
+ panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
+ }
+
+ ret, out := sliceForAppend(dst, gcmStandardNonceSize+len(plaintext)+gcmTagSize)
+ if alias.InexactOverlap(out, plaintext) {
+ panic("crypto/cipher: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("crypto/cipher: invalid buffer overlap of output and additional data")
+ }
+ nonce = out[:gcmStandardNonceSize]
+ ciphertext := out[gcmStandardNonceSize:]
+
+ // The AEAD interface allows using plaintext[:0] or ciphertext[:0] as dst.
+ //
+ // This is kind of a problem when trying to prepend or trim a nonce, because the
+ // actual AES-GCTR blocks end up overlapping but not exactly.
+ //
+ // In Open, we write the output *before* the input, so unless we do something
+ // weird like working through a chunk of block backwards, it works out.
+ //
+ // In Seal, we could work through the input backwards or intentionally load
+ // ahead before writing.
+ //
+ // However, the crypto/internal/fips/aes/gcm APIs also check for exact overlap,
+ // so for now we just do a memmove if we detect overlap.
+ //
+ // ┌───────────────────────────┬ ─ ─
+ // │PPPPPPPPPPPPPPPPPPPPPPPPPPP│ │
+ // └▽─────────────────────────▲┴ ─ ─
+ // ╲ Seal ╲
+ // ╲ Open ╲
+ // ┌───▼─────────────────────────△──┐
+ // │NN|CCCCCCCCCCCCCCCCCCCCCCCCCCC|T│
+ // └────────────────────────────────┘
+ //
+ if alias.AnyOverlap(out, plaintext) {
+ copy(ciphertext, plaintext)
+ plaintext = ciphertext[:len(plaintext)]
+ }
+
+ gcm.SealWithRandomNonce(g.GCM, nonce, ciphertext, plaintext, additionalData)
+ return ret
+}
+
+func (g gcmWithRandomNonce) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if len(nonce) != 0 {
+ panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
+ }
+ if len(ciphertext) < gcmStandardNonceSize+gcmTagSize {
+ return nil, errOpen
+ }
+
+ ret, out := sliceForAppend(dst, len(ciphertext)-gcmStandardNonceSize-gcmTagSize)
+ if alias.InexactOverlap(out, ciphertext) {
+ panic("crypto/cipher: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("crypto/cipher: invalid buffer overlap of output and additional data")
+ }
+ // See the discussion in Seal. Note that if there is any overlap at this
+ // point, it's because out = ciphertext, so out must have enough capacity
+ // even if we sliced the tag off. Also note how [AEAD] specifies that "the
+ // contents of dst, up to its capacity, may be overwritten".
+ if alias.AnyOverlap(out, ciphertext) {
+ nonce = make([]byte, gcmStandardNonceSize)
+ copy(nonce, ciphertext)
+ copy(out[:len(ciphertext)], ciphertext[gcmStandardNonceSize:])
+ ciphertext = out[:len(ciphertext)-gcmStandardNonceSize]
+ } else {
+ nonce = ciphertext[:gcmStandardNonceSize]
+ ciphertext = ciphertext[gcmStandardNonceSize:]
+ }
+
+ _, err := g.GCM.Open(out[:0], nonce, ciphertext, additionalData)
+ if err != nil {
+ return nil, err
+ }
+ return ret, nil
+}
+
// gcmAble is an interface implemented by ciphers that have a specific optimized
// implementation of GCM. crypto/aes doesn't use this anymore, and we'd like to
// eventually remove it.
t.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
}
- ciphertext := out[len(prefix):]
- // Check that the appended ciphertext wasn't affected by the prefix
- if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) {
- t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT))
+ if isDeterministic(aead) {
+ ciphertext := out[len(prefix):]
+ // Check that the appended ciphertext wasn't affected by the prefix
+ if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) {
+ t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT))
+ }
}
}
})
})
t.Run("WrongNonce", func(t *testing.T) {
-
+ if aead.NonceSize() == 0 {
+ t.Skip("AEAD does not use a nonce")
+ }
// Test all combinations of plaintext and additional data lengths.
for _, ptLen := range lengths {
for _, adLen := range lengths {
return ciphertext
}
+func isDeterministic(aead cipher.AEAD) bool {
+ // Check if the AEAD is deterministic by checking if the same plaintext
+ // encrypted with the same nonce and additional data produces the same
+ // ciphertext.
+ nonce := make([]byte, aead.NonceSize())
+ addData := []byte("additional data")
+ plaintext := []byte("plaintext")
+ ciphertext1 := aead.Seal(nil, nonce, plaintext, addData)
+ ciphertext2 := aead.Seal(nil, nonce, plaintext, addData)
+ return bytes.Equal(ciphertext1, ciphertext2)
+}
+
// Helper function to Open and authenticate ciphertext. Checks that Open
// doesn't error (assuming ciphertext was well-formed with corresponding nonce
// and additional data).