// 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" subtlemac "github.com/google/tink/go/mac/subtle" "github.com/google/tink/go/streamingaead/subtle/noncebased" "github.com/google/tink/go/subtle/random" "github.com/google/tink/go/subtle" ) const ( // AESCTRHMACNonceSizeInBytes is the size of the nonces used as IVs for CTR. AESCTRHMACNonceSizeInBytes = 16 // AESCTRHMACNoncePrefixSizeInBytes is the size of the nonce prefix. AESCTRHMACNoncePrefixSizeInBytes = 7 // AESCTRHMACKeySizeInBytes is the size of the HMAC key. AESCTRHMACKeySizeInBytes = 32 ) // AESCTRHMAC implements streaming AEAD encryption using AES-CTR and HMAC. // // Each ciphertext uses new AES-CTR and HMAC keys. 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 AESCTRHMAC struct { MainKey []byte hkdfAlg string keySizeInBytes int tagAlg string tagSizeInBytes int ciphertextSegmentSize int plaintextSegmentSize int firstCiphertextSegmentOffset int } // NewAESCTRHMAC initializes an AESCTRHMAC primitive with a key derivation key // and encryption parameters. // // mainKey is input keying material used to derive sub keys. // // hkdfAlg is a MAC algorithm name, e.g., HmacSha256, used for the HKDF key // derivation. // // keySizeInBytes is the key size of the sub keys. // // tagAlg is the MAC algorithm name, e.g. HmacSha256, used for generating per // segment tags. // // tagSizeInBytes is the size of the per segment tags. // // ciphertextSegmentSize is the size of ciphertext segments. // // firstSegmentOffset is the offset of the first ciphertext segment. func NewAESCTRHMAC(mainKey []byte, hkdfAlg string, keySizeInBytes int, tagAlg string, tagSizeInBytes, ciphertextSegmentSize, firstSegmentOffset int) (*AESCTRHMAC, 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 } if tagSizeInBytes < 10 { return nil, errors.New("tag size too small") } digestSize, err := subtle.GetHashDigestSize(tagAlg) if err != nil { return nil, err } if uint32(tagSizeInBytes) > digestSize { return nil, errors.New("tag size too big") } headerLen := 1 + keySizeInBytes + AESCTRHMACNoncePrefixSizeInBytes if ciphertextSegmentSize <= firstSegmentOffset+headerLen+tagSizeInBytes { return nil, errors.New("ciphertextSegmentSize too small") } keyClone := make([]byte, len(mainKey)) copy(keyClone, mainKey) return &AESCTRHMAC{ MainKey: keyClone, hkdfAlg: hkdfAlg, keySizeInBytes: keySizeInBytes, tagAlg: tagAlg, tagSizeInBytes: tagSizeInBytes, ciphertextSegmentSize: ciphertextSegmentSize, firstCiphertextSegmentOffset: firstSegmentOffset + headerLen, plaintextSegmentSize: ciphertextSegmentSize - tagSizeInBytes, }, nil } // HeaderLength returns the length of the encryption header. func (a *AESCTRHMAC) HeaderLength() int { return 1 + a.keySizeInBytes + AESCTRHMACNoncePrefixSizeInBytes } // deriveKeys returns an AES of size a.keySizeInBytes and an HMAC key of size AESCTRHMACKeySizeInBytes. // // They are derived from the main key using salt and aad as parameters. func (a *AESCTRHMAC) deriveKeys(salt, aad []byte) ([]byte, []byte, error) { keyMaterialSize := a.keySizeInBytes + AESCTRHMACKeySizeInBytes km, err := subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(keyMaterialSize)) if err != nil { return nil, nil, err } aesKey := km[:a.keySizeInBytes] hmacKey := km[a.keySizeInBytes:] return aesKey, hmacKey, nil } type aesCTRHMACSegmentEncrypter struct { noncebased.SegmentEncrypter blockCipher cipher.Block hmac *subtlemac.HMAC tagSizeInBytes int } func (e aesCTRHMACSegmentEncrypter) EncryptSegment(segment, nonce []byte) ([]byte, error) { sLen := len(segment) nLen := len(nonce) ctLen := sLen + e.tagSizeInBytes ciphertext := make([]byte, ctLen) stream := cipher.NewCTR(e.blockCipher, nonce) stream.XORKeyStream(ciphertext, segment) macInput := make([]byte, nLen+sLen) copy(macInput, nonce) copy(macInput[nLen:], ciphertext) tag, err := e.hmac.ComputeMAC(macInput) if err != nil { return nil, err } copy(ciphertext[sLen:], tag) return ciphertext, nil } // aesCTRHMACWriter 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 aesCTRHMACWriter // must be closed. type aesCTRHMACWriter 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 *AESCTRHMAC) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) { salt := random.GetRandomBytes(uint32(a.keySizeInBytes)) noncePrefix := random.GetRandomBytes(AESCTRHMACNoncePrefixSizeInBytes) aesKey, hmacKey, err := a.deriveKeys(salt, aad) if err != nil { return nil, err } blockCipher, err := aes.NewCipher(aesKey) if err != nil { return nil, err } hmac, err := subtlemac.NewHMAC(a.tagAlg, hmacKey, uint32(a.tagSizeInBytes)) 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: aesCTRHMACSegmentEncrypter{ blockCipher: blockCipher, hmac: hmac, tagSizeInBytes: a.tagSizeInBytes, }, NonceSize: AESCTRHMACNonceSizeInBytes, NoncePrefix: noncePrefix, PlaintextSegmentSize: a.plaintextSegmentSize, FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, }) if err != nil { return nil, err } return &aesCTRHMACWriter{Writer: nw}, nil } type aesCTRHMACSegmentDecrypter struct { noncebased.SegmentDecrypter blockCipher cipher.Block hmac *subtlemac.HMAC tagSizeInBytes int } func (d aesCTRHMACSegmentDecrypter) DecryptSegment(segment, nonce []byte) ([]byte, error) { sLen := len(segment) nLen := len(nonce) tagStart := sLen - d.tagSizeInBytes if tagStart < 0 { return nil, errors.New("segment too short") } tag := segment[tagStart:] macInput := make([]byte, nLen+tagStart) copy(macInput, nonce) copy(macInput[nLen:], segment[:tagStart]) if err := d.hmac.VerifyMAC(tag, macInput); err != nil { return nil, errors.New("tag mismatch") } result := make([]byte, tagStart) stream := cipher.NewCTR(d.blockCipher, nonce) stream.XORKeyStream(result, segment[:tagStart]) return result, nil } // aesCTRHMACReader works as a wrapper around underlying io.Reader. type aesCTRHMACReader 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 *AESCTRHMAC) 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, AESCTRHMACNoncePrefixSizeInBytes) if _, err := io.ReadFull(r, noncePrefix); err != nil { return nil, fmt.Errorf("cannot read noncePrefix: %v", err) } aesKey, hmacKey, err := a.deriveKeys(salt, aad) if err != nil { return nil, err } blockCipher, err := aes.NewCipher(aesKey) if err != nil { return nil, err } hmac, err := subtlemac.NewHMAC(a.tagAlg, hmacKey, uint32(a.tagSizeInBytes)) if err != nil { return nil, err } nr, err := noncebased.NewReader(noncebased.ReaderParams{ R: r, SegmentDecrypter: aesCTRHMACSegmentDecrypter{ blockCipher: blockCipher, hmac: hmac, tagSizeInBytes: a.tagSizeInBytes, }, NonceSize: AESCTRHMACNonceSizeInBytes, NoncePrefix: noncePrefix, CiphertextSegmentSize: a.ciphertextSegmentSize, FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, }) if err != nil { return nil, err } return &aesCTRHMACReader{Reader: nr}, nil }