mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
crypto/cipher: add NewGCMWithRandomNonce
Fixes #69981 Change-Id: I0cad11f5d7673304c5a6d85fc598ddc27ab93738 Reviewed-on: https://go-review.googlesource.com/c/go/+/629175 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
380903588c
commit
f916d93e41
1
api/next/69981.txt
Normal file
1
api/next/69981.txt
Normal file
@ -0,0 +1 @@
|
||||
pkg crypto/cipher, func NewGCMWithRandomNonce(Block) (AEAD, error) #69981
|
3
doc/next/6-stdlib/99-minor/crypto/cipher/69981.md
Normal file
3
doc/next/6-stdlib/99-minor/crypto/cipher/69981.md
Normal file
@ -0,0 +1,3 @@
|
||||
The new [NewGCMWithRandomNonce] function returns an [AEAD] that implements
|
||||
AES-GCM by generating a random nonce during Seal and prepending it to the
|
||||
ciphertext.
|
@ -67,6 +67,123 @@ func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) {
|
||||
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.
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/internal/boring"
|
||||
"crypto/internal/cryptotest"
|
||||
"crypto/internal/fips"
|
||||
fipsaes "crypto/internal/fips/aes"
|
||||
@ -723,6 +724,14 @@ func testGCMAEAD(t *testing.T, newCipher func(key []byte) cipher.Block) {
|
||||
cryptotest.TestAEAD(t, func() (cipher.AEAD, error) { return cipher.NewGCMWithNonceSize(block, nonceSize) })
|
||||
})
|
||||
}
|
||||
|
||||
// Test NewGCMWithRandomNonce.
|
||||
t.Run("GCMWithRandomNonce", func(t *testing.T) {
|
||||
if _, ok := block.(*wrapper); ok || boring.Enabled {
|
||||
t.Skip("NewGCMWithRandomNonce requires an AES block cipher")
|
||||
}
|
||||
cryptotest.TestAEAD(t, func() (cipher.AEAD, error) { return cipher.NewGCMWithRandomNonce(block) })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -208,10 +208,12 @@ func TestAEAD(t *testing.T, mAEAD MakeAEAD) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -254,7 +256,9 @@ func TestAEAD(t *testing.T, mAEAD MakeAEAD) {
|
||||
})
|
||||
|
||||
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 {
|
||||
@ -372,6 +376,18 @@ func sealMsg(t *testing.T, aead cipher.AEAD, ciphertext, nonce, plaintext, addDa
|
||||
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).
|
||||
|
Loading…
x
Reference in New Issue
Block a user