xref: /aosp_15_r20/external/tink/go/streamingaead/subtle/aes_gcm_hkdf.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
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