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 subtlemac "github.com/google/tink/go/mac/subtle" 29 "github.com/google/tink/go/streamingaead/subtle/noncebased" 30 "github.com/google/tink/go/subtle/random" 31 "github.com/google/tink/go/subtle" 32) 33 34const ( 35 // AESCTRHMACNonceSizeInBytes is the size of the nonces used as IVs for CTR. 36 AESCTRHMACNonceSizeInBytes = 16 37 38 // AESCTRHMACNoncePrefixSizeInBytes is the size of the nonce prefix. 39 AESCTRHMACNoncePrefixSizeInBytes = 7 40 41 // AESCTRHMACKeySizeInBytes is the size of the HMAC key. 42 AESCTRHMACKeySizeInBytes = 32 43) 44 45// AESCTRHMAC implements streaming AEAD encryption using AES-CTR and HMAC. 46// 47// Each ciphertext uses new AES-CTR and HMAC keys. These keys are derived using 48// HKDF and are derived from the key derivation key, a randomly chosen salt of 49// the same size as the key and a nonce prefix. 50type AESCTRHMAC struct { 51 MainKey []byte 52 hkdfAlg string 53 keySizeInBytes int 54 tagAlg string 55 tagSizeInBytes int 56 ciphertextSegmentSize int 57 plaintextSegmentSize int 58 firstCiphertextSegmentOffset int 59} 60 61// NewAESCTRHMAC initializes an AESCTRHMAC primitive with a key derivation key 62// and encryption parameters. 63// 64// mainKey is input keying material used to derive sub keys. 65// 66// hkdfAlg is a MAC algorithm name, e.g., HmacSha256, used for the HKDF key 67// derivation. 68// 69// keySizeInBytes is the key size of the sub keys. 70// 71// tagAlg is the MAC algorithm name, e.g. HmacSha256, used for generating per 72// segment tags. 73// 74// tagSizeInBytes is the size of the per segment tags. 75// 76// ciphertextSegmentSize is the size of ciphertext segments. 77// 78// firstSegmentOffset is the offset of the first ciphertext segment. 79func NewAESCTRHMAC(mainKey []byte, hkdfAlg string, keySizeInBytes int, tagAlg string, tagSizeInBytes, ciphertextSegmentSize, firstSegmentOffset int) (*AESCTRHMAC, error) { 80 if len(mainKey) < 16 || len(mainKey) < keySizeInBytes { 81 return nil, errors.New("mainKey too short") 82 } 83 if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil { 84 return nil, err 85 } 86 if tagSizeInBytes < 10 { 87 return nil, errors.New("tag size too small") 88 } 89 digestSize, err := subtle.GetHashDigestSize(tagAlg) 90 if err != nil { 91 return nil, err 92 } 93 if uint32(tagSizeInBytes) > digestSize { 94 return nil, errors.New("tag size too big") 95 } 96 headerLen := 1 + keySizeInBytes + AESCTRHMACNoncePrefixSizeInBytes 97 if ciphertextSegmentSize <= firstSegmentOffset+headerLen+tagSizeInBytes { 98 return nil, errors.New("ciphertextSegmentSize too small") 99 } 100 101 keyClone := make([]byte, len(mainKey)) 102 copy(keyClone, mainKey) 103 104 return &AESCTRHMAC{ 105 MainKey: keyClone, 106 hkdfAlg: hkdfAlg, 107 keySizeInBytes: keySizeInBytes, 108 tagAlg: tagAlg, 109 tagSizeInBytes: tagSizeInBytes, 110 ciphertextSegmentSize: ciphertextSegmentSize, 111 firstCiphertextSegmentOffset: firstSegmentOffset + headerLen, 112 plaintextSegmentSize: ciphertextSegmentSize - tagSizeInBytes, 113 }, nil 114} 115 116// HeaderLength returns the length of the encryption header. 117func (a *AESCTRHMAC) HeaderLength() int { 118 return 1 + a.keySizeInBytes + AESCTRHMACNoncePrefixSizeInBytes 119} 120 121// deriveKeys returns an AES of size a.keySizeInBytes and an HMAC key of size AESCTRHMACKeySizeInBytes. 122// 123// They are derived from the main key using salt and aad as parameters. 124func (a *AESCTRHMAC) deriveKeys(salt, aad []byte) ([]byte, []byte, error) { 125 keyMaterialSize := a.keySizeInBytes + AESCTRHMACKeySizeInBytes 126 km, err := subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(keyMaterialSize)) 127 if err != nil { 128 return nil, nil, err 129 } 130 aesKey := km[:a.keySizeInBytes] 131 hmacKey := km[a.keySizeInBytes:] 132 return aesKey, hmacKey, nil 133} 134 135type aesCTRHMACSegmentEncrypter struct { 136 noncebased.SegmentEncrypter 137 blockCipher cipher.Block 138 hmac *subtlemac.HMAC 139 tagSizeInBytes int 140} 141 142func (e aesCTRHMACSegmentEncrypter) EncryptSegment(segment, nonce []byte) ([]byte, error) { 143 sLen := len(segment) 144 nLen := len(nonce) 145 ctLen := sLen + e.tagSizeInBytes 146 ciphertext := make([]byte, ctLen) 147 148 stream := cipher.NewCTR(e.blockCipher, nonce) 149 stream.XORKeyStream(ciphertext, segment) 150 151 macInput := make([]byte, nLen+sLen) 152 copy(macInput, nonce) 153 copy(macInput[nLen:], ciphertext) 154 tag, err := e.hmac.ComputeMAC(macInput) 155 if err != nil { 156 return nil, err 157 } 158 copy(ciphertext[sLen:], tag) 159 160 return ciphertext, nil 161} 162 163// aesCTRHMACWriter works as a wrapper around underlying io.Writer, which is 164// responsible for encrypting written data. The data is encrypted and flushed 165// in segments of a given size. Once all the data is written aesCTRHMACWriter 166// must be closed. 167type aesCTRHMACWriter struct { 168 *noncebased.Writer 169} 170 171// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that 172// any write-operation via the wrapper results in AEAD-encryption of the 173// written data, using aad as associated authenticated data. The associated 174// data is not included in the ciphertext and has to be passed in as parameter 175// for decryption. 176func (a *AESCTRHMAC) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) { 177 salt := random.GetRandomBytes(uint32(a.keySizeInBytes)) 178 noncePrefix := random.GetRandomBytes(AESCTRHMACNoncePrefixSizeInBytes) 179 180 aesKey, hmacKey, err := a.deriveKeys(salt, aad) 181 if err != nil { 182 return nil, err 183 } 184 185 blockCipher, err := aes.NewCipher(aesKey) 186 if err != nil { 187 return nil, err 188 } 189 190 hmac, err := subtlemac.NewHMAC(a.tagAlg, hmacKey, uint32(a.tagSizeInBytes)) 191 if err != nil { 192 return nil, err 193 } 194 195 header := make([]byte, a.HeaderLength()) 196 header[0] = byte(a.HeaderLength()) 197 copy(header[1:], salt) 198 copy(header[1+len(salt):], noncePrefix) 199 if _, err := w.Write(header); err != nil { 200 return nil, err 201 } 202 203 nw, err := noncebased.NewWriter(noncebased.WriterParams{ 204 W: w, 205 SegmentEncrypter: aesCTRHMACSegmentEncrypter{ 206 blockCipher: blockCipher, 207 hmac: hmac, 208 tagSizeInBytes: a.tagSizeInBytes, 209 }, 210 NonceSize: AESCTRHMACNonceSizeInBytes, 211 NoncePrefix: noncePrefix, 212 PlaintextSegmentSize: a.plaintextSegmentSize, 213 FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, 214 }) 215 if err != nil { 216 return nil, err 217 } 218 return &aesCTRHMACWriter{Writer: nw}, nil 219} 220 221type aesCTRHMACSegmentDecrypter struct { 222 noncebased.SegmentDecrypter 223 blockCipher cipher.Block 224 hmac *subtlemac.HMAC 225 tagSizeInBytes int 226} 227 228func (d aesCTRHMACSegmentDecrypter) DecryptSegment(segment, nonce []byte) ([]byte, error) { 229 sLen := len(segment) 230 nLen := len(nonce) 231 tagStart := sLen - d.tagSizeInBytes 232 if tagStart < 0 { 233 return nil, errors.New("segment too short") 234 } 235 tag := segment[tagStart:] 236 237 macInput := make([]byte, nLen+tagStart) 238 copy(macInput, nonce) 239 copy(macInput[nLen:], segment[:tagStart]) 240 if err := d.hmac.VerifyMAC(tag, macInput); err != nil { 241 return nil, errors.New("tag mismatch") 242 } 243 244 result := make([]byte, tagStart) 245 stream := cipher.NewCTR(d.blockCipher, nonce) 246 stream.XORKeyStream(result, segment[:tagStart]) 247 return result, nil 248} 249 250// aesCTRHMACReader works as a wrapper around underlying io.Reader. 251type aesCTRHMACReader struct { 252 *noncebased.Reader 253} 254 255// NewDecryptingReader returns a wrapper around underlying io.Reader, such that 256// any read-operation via the wrapper results in AEAD-decryption of the 257// underlying ciphertext, using aad as associated authenticated data. 258func (a *AESCTRHMAC) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) { 259 hlen := make([]byte, 1) 260 if _, err := io.ReadFull(r, hlen); err != nil { 261 return nil, err 262 } 263 if hlen[0] != byte(a.HeaderLength()) { 264 return nil, errors.New("invalid header length") 265 } 266 267 salt := make([]byte, a.keySizeInBytes) 268 if _, err := io.ReadFull(r, salt); err != nil { 269 return nil, fmt.Errorf("cannot read salt: %v", err) 270 } 271 272 noncePrefix := make([]byte, AESCTRHMACNoncePrefixSizeInBytes) 273 if _, err := io.ReadFull(r, noncePrefix); err != nil { 274 return nil, fmt.Errorf("cannot read noncePrefix: %v", err) 275 } 276 277 aesKey, hmacKey, err := a.deriveKeys(salt, aad) 278 if err != nil { 279 return nil, err 280 } 281 282 blockCipher, err := aes.NewCipher(aesKey) 283 if err != nil { 284 return nil, err 285 } 286 287 hmac, err := subtlemac.NewHMAC(a.tagAlg, hmacKey, uint32(a.tagSizeInBytes)) 288 if err != nil { 289 return nil, err 290 } 291 292 nr, err := noncebased.NewReader(noncebased.ReaderParams{ 293 R: r, 294 SegmentDecrypter: aesCTRHMACSegmentDecrypter{ 295 blockCipher: blockCipher, 296 hmac: hmac, 297 tagSizeInBytes: a.tagSizeInBytes, 298 }, 299 NonceSize: AESCTRHMACNonceSizeInBytes, 300 NoncePrefix: noncePrefix, 301 CiphertextSegmentSize: a.ciphertextSegmentSize, 302 FirstCiphertextSegmentOffset: a.firstCiphertextSegmentOffset, 303 }) 304 if err != nil { 305 return nil, err 306 } 307 308 return &aesCTRHMACReader{Reader: nr}, nil 309} 310