xref: /aosp_15_r20/external/tink/go/aead/subtle/encrypt_then_authenticate.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	"encoding/binary"
21	"errors"
22	"fmt"
23
24	"github.com/google/tink/go/tink"
25)
26
27// EncryptThenAuthenticate performs an encrypt-then-MAC operation on plaintext
28// and associated data (ad). The MAC is computed over (ad ||
29// ciphertext || size of ad). This implementation is based on
30// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
31type EncryptThenAuthenticate struct {
32	indCPACipher INDCPACipher
33	mac          tink.MAC
34	tagSize      int
35}
36
37const (
38	minTagSizeInBytes = 10
39)
40
41// Assert that EncryptThenAuthenticate implements the AEAD interface.
42var _ tink.AEAD = (*EncryptThenAuthenticate)(nil)
43
44// uint64ToByte stores a uint64 to a slice of bytes in big endian format.
45func uint64ToByte(n uint64) []byte {
46	buf := make([]byte, 8)
47	binary.BigEndian.PutUint64(buf, n)
48	return buf
49}
50
51// NewEncryptThenAuthenticate returns a new instance of EncryptThenAuthenticate.
52func NewEncryptThenAuthenticate(indCPACipher INDCPACipher, mac tink.MAC, tagSize int) (*EncryptThenAuthenticate, error) {
53	if tagSize < minTagSizeInBytes {
54		return nil, fmt.Errorf("encrypt_then_authenticate: tag size too small")
55	}
56	return &EncryptThenAuthenticate{indCPACipher, mac, tagSize}, nil
57}
58
59// Encrypt encrypts plaintext with associatedData.
60// The resulting ciphertext allows for checking authenticity and
61// integrity of associatedData, but does not guarantee its secrecy.
62//
63// The plaintext is encrypted with an INDCPACipher, then MAC is computed over
64// (associatedData || ciphertext || n) where n is associatedData's length
65// in bits represented as a 64-bit bigendian unsigned integer. The final
66// ciphertext format is (IND-CPA ciphertext || mac).
67func (e *EncryptThenAuthenticate) Encrypt(plaintext, associatedData []byte) ([]byte, error) {
68	ciphertext, err := e.indCPACipher.Encrypt(plaintext)
69	if err != nil {
70		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
71	}
72
73	adSizeInBits := uint64(len(associatedData)) * 8
74	adSizeInBitsEncoded := uint64ToByte(adSizeInBits)
75	toAuthData := make([]byte, 0, len(associatedData)+len(ciphertext)+len(adSizeInBitsEncoded))
76	toAuthData = append(toAuthData, associatedData...)
77	toAuthData = append(toAuthData, ciphertext...)
78	toAuthData = append(toAuthData, adSizeInBitsEncoded...)
79	tag, err := e.mac.ComputeMAC(toAuthData)
80	if err != nil {
81		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
82	}
83
84	if len(tag) != e.tagSize {
85		return nil, errors.New("encrypt_then_authenticate: invalid tag size")
86	}
87
88	ciphertext = append(ciphertext, tag...)
89	return ciphertext, nil
90}
91
92// Decrypt decrypts ciphertext with associatedData.
93func (e *EncryptThenAuthenticate) Decrypt(ciphertext, associatedData []byte) ([]byte, error) {
94	if len(ciphertext) < e.tagSize {
95		return nil, errors.New("ciphertext too short")
96	}
97
98	// payload contains everything except the tag.
99	payload := ciphertext[:len(ciphertext)-e.tagSize]
100
101	// Authenticate the following data:
102	// associatedData || payload || adSizeInBits
103	adSizeInBits := uint64(len(associatedData)) * 8
104	adSizeInBitsEncoded := uint64ToByte(adSizeInBits)
105	toAuthData := make([]byte, 0, len(associatedData)+len(payload)+len(adSizeInBitsEncoded))
106	toAuthData = append(toAuthData, associatedData...)
107	toAuthData = append(toAuthData, payload...)
108	toAuthData = append(toAuthData, adSizeInBitsEncoded...)
109
110	err := e.mac.VerifyMAC(ciphertext[len(ciphertext)-e.tagSize:], toAuthData)
111	if err != nil {
112		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
113	}
114
115	plaintext, err := e.indCPACipher.Decrypt(payload)
116	if err != nil {
117		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
118	}
119
120	return plaintext, nil
121}
122