1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15//////////////////////////////////////////////////////////////////////////////// 16 17package subtle 18 19import ( 20 "crypto/aes" 21 "crypto/cipher" 22 "errors" 23 "fmt" 24 "io" 25 26 // Placeholder for internal crypto/cipher allowlist, please ignore. 27 subtleaead "github.com/google/tink/go/aead/subtle" 28 "github.com/google/tink/go/streamingaead/subtle/noncebased" 29 "github.com/google/tink/go/subtle/random" 30 "github.com/google/tink/go/subtle" 31) 32 33const ( 34 // AESGCMHKDFNonceSizeInBytes is the size of the nonces used for GCM. 35 AESGCMHKDFNonceSizeInBytes = 12 36 37 // AESGCMHKDFNoncePrefixSizeInBytes is the size of the randomly generated 38 // nonce prefix. 39 AESGCMHKDFNoncePrefixSizeInBytes = 7 40 41 // AESGCMHKDFTagSizeInBytes is the size of the tags of each ciphertext 42 // segment. 43 AESGCMHKDFTagSizeInBytes = 16 44) 45 46// AESGCMHKDF implements streaming AEAD encryption using AES-GCM. 47// 48// Each ciphertext uses a new AES-GCM key. These keys are derived using HKDF 49// and are derived from the key derivation key, a randomly chosen salt of the 50// same size as the key and a nonce prefix. 51type AESGCMHKDF struct { 52 MainKey []byte 53 hkdfAlg string 54 keySizeInBytes int 55 ciphertextSegmentSize int 56 firstCiphertextSegmentOffset int 57 plaintextSegmentSize int 58} 59 60// NewAESGCMHKDF initializes a streaming primitive with a key derivation key 61// and encryption parameters. 62// 63// mainKey is an input keying material used to derive sub keys. 64// 65// hkdfAlg is a MAC algorithm name, e.g., HmacSha256, used for the HKDF key 66// derivation. 67// 68// keySizeInBytes argument is a key size of the sub keys. 69// 70// ciphertextSegmentSize argument is the size of ciphertext segments. 71// 72// firstSegmentOffset argument is the offset of the first ciphertext segment. 73func NewAESGCMHKDF(mainKey []byte, hkdfAlg string, keySizeInBytes, ciphertextSegmentSize, firstSegmentOffset int) (*AESGCMHKDF, error) { 74 if len(mainKey) < 16 || len(mainKey) < keySizeInBytes { 75 return nil, errors.New("mainKey too short") 76 } 77 if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil { 78 return nil, err 79 } 80 headerLen := 1 + keySizeInBytes + AESGCMHKDFNoncePrefixSizeInBytes 81 if ciphertextSegmentSize <= firstSegmentOffset+headerLen+AESGCMHKDFTagSizeInBytes { 82 return nil, errors.New("ciphertextSegmentSize too small") 83 } 84 85 keyClone := make([]byte, len(mainKey)) 86 copy(keyClone, mainKey) 87 88 return &AESGCMHKDF{ 89 MainKey: keyClone, 90 hkdfAlg: hkdfAlg, 91 keySizeInBytes: keySizeInBytes, 92 ciphertextSegmentSize: ciphertextSegmentSize, 93 firstCiphertextSegmentOffset: firstSegmentOffset + headerLen, 94 plaintextSegmentSize: ciphertextSegmentSize - AESGCMHKDFTagSizeInBytes, 95 }, nil 96} 97 98// HeaderLength returns the length of the encryption header. 99func (a *AESGCMHKDF) HeaderLength() int { 100 return 1 + a.keySizeInBytes + AESGCMHKDFNoncePrefixSizeInBytes 101} 102 103// deriveKey returns a key derived from the given main key using salt and aad 104// parameters. 105func (a *AESGCMHKDF) deriveKey(salt, aad []byte) ([]byte, error) { 106 return subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(a.keySizeInBytes)) 107} 108 109// newCipher creates a new AES-GCM cipher using the given key and the crypto library. 110func (a *AESGCMHKDF) newCipher(key []byte) (cipher.AEAD, error) { 111 aesCipher, err := aes.NewCipher(key) 112 if err != nil { 113 return nil, err 114 } 115 aesGCMCipher, err := cipher.NewGCMWithTagSize(aesCipher, AESGCMHKDFTagSizeInBytes) 116 if err != nil { 117 return nil, err 118 } 119 return aesGCMCipher, nil 120} 121 122type aesGCMHKDFSegmentEncrypter struct { 123 noncebased.SegmentEncrypter 124 cipher cipher.AEAD 125} 126 127func (e aesGCMHKDFSegmentEncrypter) EncryptSegment(segment, nonce []byte) ([]byte, error) { 128 result := make([]byte, 0, len(segment)) 129 result = e.cipher.Seal(result, nonce, segment, nil) 130 return result, nil 131} 132 133// aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is 134// responsible for encrypting written data. The data is encrypted and flushed 135// in segments of a given size. Once all the data is written aesGCMHKDFWriter 136// must be closed. 137type aesGCMHKDFWriter struct { 138 *noncebased.Writer 139} 140 141// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that 142// any write-operation via the wrapper results in AEAD-encryption of the 143// written data, using aad as associated authenticated data. The associated 144// data is not included in the ciphertext and has to be passed in as parameter 145// for decryption. 146func (a *AESGCMHKDF) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) { 147 salt := random.GetRandomBytes(uint32(a.keySizeInBytes)) 148 noncePrefix := random.GetRandomBytes(AESGCMHKDFNoncePrefixSizeInBytes) 149 150 dkey, err := a.deriveKey(salt, aad) 151 if err != nil { 152 return nil, err 153 } 154 155 cipher, err := a.newCipher(dkey) 156 if err != nil { 157 return nil, err 158 } 159 160 header := make([]byte, a.HeaderLength()) 161 header[0] = byte(a.HeaderLength()) 162 copy(header[1:], salt) 163 copy(header[1+len(salt):], noncePrefix) 164 if _, err := w.Write(header); err != nil { 165 return nil, err 166 } 167 168 nw, err := noncebased.NewWriter(noncebased.WriterParams{ 169 W: w, 170 SegmentEncrypter: aesGCMHKDFSegmentEncrypter{cipher: cipher}, 171 NonceSize: AESGCMHKDFNonceSizeInBytes, 172 NoncePrefix: noncePrefix, 173 PlaintextSegmentSize: a.plaintextSegmentSize, 174 FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, 175 }) 176 if err != nil { 177 return nil, err 178 } 179 180 return &aesGCMHKDFWriter{Writer: nw}, nil 181} 182 183type aesGCMHKDFSegmentDecrypter struct { 184 noncebased.SegmentDecrypter 185 cipher cipher.AEAD 186} 187 188func (d aesGCMHKDFSegmentDecrypter) DecryptSegment(segment, nonce []byte) ([]byte, error) { 189 result := make([]byte, 0, len(segment)) 190 result, err := d.cipher.Open(result, nonce, segment, nil) 191 if err != nil { 192 return nil, err 193 } 194 return result, nil 195} 196 197// aesGCMHKDFReader works as a wrapper around underlying io.Reader. 198type aesGCMHKDFReader struct { 199 *noncebased.Reader 200} 201 202// NewDecryptingReader returns a wrapper around underlying io.Reader, such that 203// any read-operation via the wrapper results in AEAD-decryption of the 204// underlying ciphertext, using aad as associated authenticated data. 205func (a *AESGCMHKDF) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) { 206 hlen := make([]byte, 1) 207 if _, err := io.ReadFull(r, hlen); err != nil { 208 return nil, err 209 } 210 if hlen[0] != byte(a.HeaderLength()) { 211 return nil, errors.New("invalid header length") 212 } 213 214 salt := make([]byte, a.keySizeInBytes) 215 if _, err := io.ReadFull(r, salt); err != nil { 216 return nil, fmt.Errorf("cannot read salt: %v", err) 217 } 218 219 noncePrefix := make([]byte, AESGCMHKDFNoncePrefixSizeInBytes) 220 if _, err := io.ReadFull(r, noncePrefix); err != nil { 221 return nil, fmt.Errorf("cannot read noncePrefix: %v", err) 222 } 223 224 dkey, err := a.deriveKey(salt, aad) 225 if err != nil { 226 return nil, err 227 } 228 229 cipher, err := a.newCipher(dkey) 230 if err != nil { 231 return nil, err 232 } 233 234 nr, err := noncebased.NewReader(noncebased.ReaderParams{ 235 R: r, 236 SegmentDecrypter: aesGCMHKDFSegmentDecrypter{cipher: cipher}, 237 NonceSize: AESGCMHKDFNonceSizeInBytes, 238 NoncePrefix: noncePrefix, 239 CiphertextSegmentSize: a.ciphertextSegmentSize, 240 FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, 241 }) 242 if err != nil { 243 return nil, err 244 } 245 246 return &aesGCMHKDFReader{Reader: nr}, nil 247} 248