| // Copyright 2020 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package subtle |
| |
| import ( |
| "crypto/aes" |
| "crypto/cipher" |
| "errors" |
| "fmt" |
| "io" |
| |
| // Placeholder for internal crypto/cipher allowlist, please ignore. |
| subtleaead "github.com/google/tink/go/aead/subtle" |
| "github.com/google/tink/go/streamingaead/subtle/noncebased" |
| "github.com/google/tink/go/subtle/random" |
| "github.com/google/tink/go/subtle" |
| ) |
| |
| const ( |
| // AESGCMHKDFNonceSizeInBytes is the size of the nonces used for GCM. |
| AESGCMHKDFNonceSizeInBytes = 12 |
| |
| // AESGCMHKDFNoncePrefixSizeInBytes is the size of the randomly generated |
| // nonce prefix. |
| AESGCMHKDFNoncePrefixSizeInBytes = 7 |
| |
| // AESGCMHKDFTagSizeInBytes is the size of the tags of each ciphertext |
| // segment. |
| AESGCMHKDFTagSizeInBytes = 16 |
| ) |
| |
| // AESGCMHKDF implements streaming AEAD encryption using AES-GCM. |
| // |
| // Each ciphertext uses a new AES-GCM key. These keys are derived using HKDF |
| // and are derived from the key derivation key, a randomly chosen salt of the |
| // same size as the key and a nonce prefix. |
| type AESGCMHKDF struct { |
| MainKey []byte |
| hkdfAlg string |
| keySizeInBytes int |
| ciphertextSegmentSize int |
| firstCiphertextSegmentOffset int |
| plaintextSegmentSize int |
| } |
| |
| // NewAESGCMHKDF initializes a streaming primitive with a key derivation key |
| // and encryption parameters. |
| // |
| // mainKey is an input keying material used to derive sub keys. |
| // |
| // hkdfAlg is a MAC algorithm name, e.g., HmacSha256, used for the HKDF key |
| // derivation. |
| // |
| // keySizeInBytes argument is a key size of the sub keys. |
| // |
| // ciphertextSegmentSize argument is the size of ciphertext segments. |
| // |
| // firstSegmentOffset argument is the offset of the first ciphertext segment. |
| func NewAESGCMHKDF(mainKey []byte, hkdfAlg string, keySizeInBytes, ciphertextSegmentSize, firstSegmentOffset int) (*AESGCMHKDF, error) { |
| if len(mainKey) < 16 || len(mainKey) < keySizeInBytes { |
| return nil, errors.New("mainKey too short") |
| } |
| if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil { |
| return nil, err |
| } |
| headerLen := 1 + keySizeInBytes + AESGCMHKDFNoncePrefixSizeInBytes |
| if ciphertextSegmentSize <= firstSegmentOffset+headerLen+AESGCMHKDFTagSizeInBytes { |
| return nil, errors.New("ciphertextSegmentSize too small") |
| } |
| |
| keyClone := make([]byte, len(mainKey)) |
| copy(keyClone, mainKey) |
| |
| return &AESGCMHKDF{ |
| MainKey: keyClone, |
| hkdfAlg: hkdfAlg, |
| keySizeInBytes: keySizeInBytes, |
| ciphertextSegmentSize: ciphertextSegmentSize, |
| firstCiphertextSegmentOffset: firstSegmentOffset + headerLen, |
| plaintextSegmentSize: ciphertextSegmentSize - AESGCMHKDFTagSizeInBytes, |
| }, nil |
| } |
| |
| // HeaderLength returns the length of the encryption header. |
| func (a *AESGCMHKDF) HeaderLength() int { |
| return 1 + a.keySizeInBytes + AESGCMHKDFNoncePrefixSizeInBytes |
| } |
| |
| // deriveKey returns a key derived from the given main key using salt and aad |
| // parameters. |
| func (a *AESGCMHKDF) deriveKey(salt, aad []byte) ([]byte, error) { |
| return subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(a.keySizeInBytes)) |
| } |
| |
| // newCipher creates a new AES-GCM cipher using the given key and the crypto library. |
| func (a *AESGCMHKDF) newCipher(key []byte) (cipher.AEAD, error) { |
| aesCipher, err := aes.NewCipher(key) |
| if err != nil { |
| return nil, err |
| } |
| aesGCMCipher, err := cipher.NewGCMWithTagSize(aesCipher, AESGCMHKDFTagSizeInBytes) |
| if err != nil { |
| return nil, err |
| } |
| return aesGCMCipher, nil |
| } |
| |
| type aesGCMHKDFSegmentEncrypter struct { |
| noncebased.SegmentEncrypter |
| cipher cipher.AEAD |
| } |
| |
| func (e aesGCMHKDFSegmentEncrypter) EncryptSegment(segment, nonce []byte) ([]byte, error) { |
| result := make([]byte, 0, len(segment)) |
| result = e.cipher.Seal(result, nonce, segment, nil) |
| return result, nil |
| } |
| |
| // aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is |
| // responsible for encrypting written data. The data is encrypted and flushed |
| // in segments of a given size. Once all the data is written aesGCMHKDFWriter |
| // must be closed. |
| type aesGCMHKDFWriter struct { |
| *noncebased.Writer |
| } |
| |
| // NewEncryptingWriter returns a wrapper around underlying io.Writer, such that |
| // any write-operation via the wrapper results in AEAD-encryption of the |
| // written data, using aad as associated authenticated data. The associated |
| // data is not included in the ciphertext and has to be passed in as parameter |
| // for decryption. |
| func (a *AESGCMHKDF) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) { |
| salt := random.GetRandomBytes(uint32(a.keySizeInBytes)) |
| noncePrefix := random.GetRandomBytes(AESGCMHKDFNoncePrefixSizeInBytes) |
| |
| dkey, err := a.deriveKey(salt, aad) |
| if err != nil { |
| return nil, err |
| } |
| |
| cipher, err := a.newCipher(dkey) |
| if err != nil { |
| return nil, err |
| } |
| |
| header := make([]byte, a.HeaderLength()) |
| header[0] = byte(a.HeaderLength()) |
| copy(header[1:], salt) |
| copy(header[1+len(salt):], noncePrefix) |
| if _, err := w.Write(header); err != nil { |
| return nil, err |
| } |
| |
| nw, err := noncebased.NewWriter(noncebased.WriterParams{ |
| W: w, |
| SegmentEncrypter: aesGCMHKDFSegmentEncrypter{cipher: cipher}, |
| NonceSize: AESGCMHKDFNonceSizeInBytes, |
| NoncePrefix: noncePrefix, |
| PlaintextSegmentSize: a.plaintextSegmentSize, |
| FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &aesGCMHKDFWriter{Writer: nw}, nil |
| } |
| |
| type aesGCMHKDFSegmentDecrypter struct { |
| noncebased.SegmentDecrypter |
| cipher cipher.AEAD |
| } |
| |
| func (d aesGCMHKDFSegmentDecrypter) DecryptSegment(segment, nonce []byte) ([]byte, error) { |
| result := make([]byte, 0, len(segment)) |
| result, err := d.cipher.Open(result, nonce, segment, nil) |
| if err != nil { |
| return nil, err |
| } |
| return result, nil |
| } |
| |
| // aesGCMHKDFReader works as a wrapper around underlying io.Reader. |
| type aesGCMHKDFReader struct { |
| *noncebased.Reader |
| } |
| |
| // NewDecryptingReader returns a wrapper around underlying io.Reader, such that |
| // any read-operation via the wrapper results in AEAD-decryption of the |
| // underlying ciphertext, using aad as associated authenticated data. |
| func (a *AESGCMHKDF) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) { |
| hlen := make([]byte, 1) |
| if _, err := io.ReadFull(r, hlen); err != nil { |
| return nil, err |
| } |
| if hlen[0] != byte(a.HeaderLength()) { |
| return nil, errors.New("invalid header length") |
| } |
| |
| salt := make([]byte, a.keySizeInBytes) |
| if _, err := io.ReadFull(r, salt); err != nil { |
| return nil, fmt.Errorf("cannot read salt: %v", err) |
| } |
| |
| noncePrefix := make([]byte, AESGCMHKDFNoncePrefixSizeInBytes) |
| if _, err := io.ReadFull(r, noncePrefix); err != nil { |
| return nil, fmt.Errorf("cannot read noncePrefix: %v", err) |
| } |
| |
| dkey, err := a.deriveKey(salt, aad) |
| if err != nil { |
| return nil, err |
| } |
| |
| cipher, err := a.newCipher(dkey) |
| if err != nil { |
| return nil, err |
| } |
| |
| nr, err := noncebased.NewReader(noncebased.ReaderParams{ |
| R: r, |
| SegmentDecrypter: aesGCMHKDFSegmentDecrypter{cipher: cipher}, |
| NonceSize: AESGCMHKDFNonceSizeInBytes, |
| NoncePrefix: noncePrefix, |
| CiphertextSegmentSize: a.ciphertextSegmentSize, |
| FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &aesGCMHKDFReader{Reader: nr}, nil |
| } |