| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package cipher |
| |
| import ( |
| "crypto/internal/fips140/aes" |
| "crypto/internal/fips140/aes/gcm" |
| "crypto/internal/fips140/alias" |
| "crypto/internal/fips140only" |
| "crypto/subtle" |
| "errors" |
| "internal/byteorder" |
| ) |
| |
| const ( |
| gcmBlockSize = 16 |
| gcmStandardNonceSize = 12 |
| gcmTagSize = 16 |
| gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. |
| ) |
| |
| // NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode |
| // with the standard nonce length. |
| // |
| // In general, the GHASH operation performed by this implementation of GCM is not constant-time. |
| // An exception is when the underlying [Block] was created by aes.NewCipher |
| // on systems with hardware support for AES. See the [crypto/aes] package documentation for details. |
| func NewGCM(cipher Block) (AEAD, error) { |
| if fips140only.Enabled { |
| return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") |
| } |
| return newGCM(cipher, gcmStandardNonceSize, gcmTagSize) |
| } |
| |
| // NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois |
| // Counter Mode, which accepts nonces of the given length. The length must not |
| // be zero. |
| // |
| // Only use this function if you require compatibility with an existing |
| // cryptosystem that uses non-standard nonce lengths. All other users should use |
| // [NewGCM], which is faster and more resistant to misuse. |
| func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { |
| if fips140only.Enabled { |
| return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") |
| } |
| return newGCM(cipher, size, gcmTagSize) |
| } |
| |
| // NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois |
| // Counter Mode, which generates tags with the given length. |
| // |
| // Tag sizes between 12 and 16 bytes are allowed. |
| // |
| // Only use this function if you require compatibility with an existing |
| // cryptosystem that uses non-standard tag lengths. All other users should use |
| // [NewGCM], which is more resistant to misuse. |
| func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) { |
| if fips140only.Enabled { |
| return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") |
| } |
| return newGCM(cipher, gcmStandardNonceSize, tagSize) |
| } |
| |
| func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) { |
| c, ok := cipher.(*aes.Block) |
| if !ok { |
| if fips140only.Enabled { |
| return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode") |
| } |
| return newGCMFallback(cipher, nonceSize, tagSize) |
| } |
| // We don't return gcm.New directly, because it would always return a non-nil |
| // AEAD interface value with type *gcm.GCM even if the *gcm.GCM is nil. |
| g, err := gcm.New(c, nonceSize, tagSize) |
| if err != nil { |
| return nil, err |
| } |
| return g, nil |
| } |
| |
| // NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter |
| // Mode, with randomly-generated nonces. The cipher must have been created by |
| // [crypto/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/fips140/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. |
| type gcmAble interface { |
| NewGCM(nonceSize, tagSize int) (AEAD, error) |
| } |
| |
| func newGCMFallback(cipher Block, nonceSize, tagSize int) (AEAD, error) { |
| if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { |
| return nil, errors.New("cipher: incorrect tag size given to GCM") |
| } |
| if nonceSize <= 0 { |
| return nil, errors.New("cipher: the nonce can't have zero length") |
| } |
| if cipher, ok := cipher.(gcmAble); ok { |
| return cipher.NewGCM(nonceSize, tagSize) |
| } |
| if cipher.BlockSize() != gcmBlockSize { |
| return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") |
| } |
| return &gcmFallback{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}, nil |
| } |
| |
| // gcmFallback is only used for non-AES ciphers, which regrettably we |
| // theoretically support. It's a copy of the generic implementation from |
| // crypto/internal/fips140/aes/gcm/gcm_generic.go, refer to that file for more details. |
| type gcmFallback struct { |
| cipher Block |
| nonceSize int |
| tagSize int |
| } |
| |
| func (g *gcmFallback) NonceSize() int { |
| return g.nonceSize |
| } |
| |
| func (g *gcmFallback) Overhead() int { |
| return g.tagSize |
| } |
| |
| func (g *gcmFallback) Seal(dst, nonce, plaintext, additionalData []byte) []byte { |
| if len(nonce) != g.nonceSize { |
| panic("crypto/cipher: incorrect nonce length given to GCM") |
| } |
| if g.nonceSize == 0 { |
| panic("crypto/cipher: incorrect GCM nonce size") |
| } |
| if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { |
| panic("crypto/cipher: message too large for GCM") |
| } |
| |
| ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) |
| 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") |
| } |
| |
| var H, counter, tagMask [gcmBlockSize]byte |
| g.cipher.Encrypt(H[:], H[:]) |
| deriveCounter(&H, &counter, nonce) |
| gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter) |
| |
| gcmCounterCryptGeneric(g.cipher, out, plaintext, &counter) |
| |
| var tag [gcmTagSize]byte |
| gcmAuth(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData) |
| copy(out[len(plaintext):], tag[:]) |
| |
| return ret |
| } |
| |
| var errOpen = errors.New("cipher: message authentication failed") |
| |
| func (g *gcmFallback) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { |
| if len(nonce) != g.nonceSize { |
| panic("crypto/cipher: incorrect nonce length given to GCM") |
| } |
| if g.tagSize < gcmMinimumTagSize { |
| panic("crypto/cipher: incorrect GCM tag size") |
| } |
| |
| if len(ciphertext) < g.tagSize { |
| return nil, errOpen |
| } |
| if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) { |
| return nil, errOpen |
| } |
| |
| ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize) |
| 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") |
| } |
| |
| var H, counter, tagMask [gcmBlockSize]byte |
| g.cipher.Encrypt(H[:], H[:]) |
| deriveCounter(&H, &counter, nonce) |
| gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter) |
| |
| tag := ciphertext[len(ciphertext)-g.tagSize:] |
| ciphertext = ciphertext[:len(ciphertext)-g.tagSize] |
| |
| var expectedTag [gcmTagSize]byte |
| gcmAuth(expectedTag[:], &H, &tagMask, ciphertext, additionalData) |
| if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { |
| // We sometimes decrypt and authenticate concurrently, so we overwrite |
| // dst in the event of a tag mismatch. To be consistent across platforms |
| // and to avoid releasing unauthenticated plaintext, we clear the buffer |
| // in the event of an error. |
| clear(out) |
| return nil, errOpen |
| } |
| |
| gcmCounterCryptGeneric(g.cipher, out, ciphertext, &counter) |
| |
| return ret, nil |
| } |
| |
| func deriveCounter(H, counter *[gcmBlockSize]byte, nonce []byte) { |
| if len(nonce) == gcmStandardNonceSize { |
| copy(counter[:], nonce) |
| counter[gcmBlockSize-1] = 1 |
| } else { |
| lenBlock := make([]byte, 16) |
| byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8) |
| J := gcm.GHASH(H, nonce, lenBlock) |
| copy(counter[:], J) |
| } |
| } |
| |
| func gcmCounterCryptGeneric(b Block, out, src []byte, counter *[gcmBlockSize]byte) { |
| var mask [gcmBlockSize]byte |
| for len(src) >= gcmBlockSize { |
| b.Encrypt(mask[:], counter[:]) |
| gcmInc32(counter) |
| |
| subtle.XORBytes(out, src, mask[:]) |
| out = out[gcmBlockSize:] |
| src = src[gcmBlockSize:] |
| } |
| if len(src) > 0 { |
| b.Encrypt(mask[:], counter[:]) |
| gcmInc32(counter) |
| subtle.XORBytes(out, src, mask[:]) |
| } |
| } |
| |
| func gcmInc32(counterBlock *[gcmBlockSize]byte) { |
| ctr := counterBlock[len(counterBlock)-4:] |
| byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1) |
| } |
| |
| func gcmAuth(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { |
| lenBlock := make([]byte, 16) |
| byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8) |
| byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8) |
| S := gcm.GHASH(H, additionalData, ciphertext, lenBlock) |
| subtle.XORBytes(out, S, tagMask[:]) |
| } |
| |
| // sliceForAppend takes a slice and a requested number of bytes. It returns a |
| // slice with the contents of the given slice followed by that many bytes and a |
| // second slice that aliases into it and contains only the extra bytes. If the |
| // original slice has sufficient capacity then no allocation is performed. |
| func sliceForAppend(in []byte, n int) (head, tail []byte) { |
| if total := len(in) + n; cap(in) >= total { |
| head = in[:total] |
| } else { |
| head = make([]byte, total) |
| copy(head, in) |
| } |
| tail = head[len(in):] |
| return |
| } |