main
1// Copyright (C) 2019 ProtonTech AG
2
3// Package eax provides an implementation of the EAX
4// (encrypt-authenticate-translate) mode of operation, as described in
5// Bellare, Rogaway, and Wagner "THE EAX MODE OF OPERATION: A TWO-PASS
6// AUTHENTICATED-ENCRYPTION SCHEME OPTIMIZED FOR SIMPLICITY AND EFFICIENCY."
7// In FSE'04, volume 3017 of LNCS, 2004
8package eax
9
10import (
11 "crypto/cipher"
12 "crypto/subtle"
13 "errors"
14 "github.com/ProtonMail/go-crypto/internal/byteutil"
15)
16
17const (
18 defaultTagSize = 16
19 defaultNonceSize = 16
20)
21
22type eax struct {
23 block cipher.Block // Only AES-{128, 192, 256} supported
24 tagSize int // At least 12 bytes recommended
25 nonceSize int
26}
27
28func (e *eax) NonceSize() int {
29 return e.nonceSize
30}
31
32func (e *eax) Overhead() int {
33 return e.tagSize
34}
35
36// NewEAX returns an EAX instance with AES-{KEYLENGTH} and default nonce and
37// tag lengths. Supports {128, 192, 256}- bit key length.
38func NewEAX(block cipher.Block) (cipher.AEAD, error) {
39 return NewEAXWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize)
40}
41
42// NewEAXWithNonceAndTagSize returns an EAX instance with AES-{keyLength} and
43// given nonce and tag lengths in bytes. Panics on zero nonceSize and
44// exceedingly long tags.
45//
46// It is recommended to use at least 12 bytes as tag length (see, for instance,
47// NIST SP 800-38D).
48//
49// Only to be used for compatibility with existing cryptosystems with
50// non-standard parameters. For all other cases, prefer NewEAX.
51func NewEAXWithNonceAndTagSize(
52 block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) {
53 if nonceSize < 1 {
54 return nil, eaxError("Cannot initialize EAX with nonceSize = 0")
55 }
56 if tagSize > block.BlockSize() {
57 return nil, eaxError("Custom tag length exceeds blocksize")
58 }
59 return &eax{
60 block: block,
61 tagSize: tagSize,
62 nonceSize: nonceSize,
63 }, nil
64}
65
66func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
67 if len(nonce) > e.nonceSize {
68 panic("crypto/eax: Nonce too long for this instance")
69 }
70 ret, out := byteutil.SliceForAppend(dst, len(plaintext)+e.tagSize)
71 omacNonce := e.omacT(0, nonce)
72 omacAdata := e.omacT(1, adata)
73
74 // Encrypt message using CTR mode and omacNonce as IV
75 ctr := cipher.NewCTR(e.block, omacNonce)
76 ciphertextData := out[:len(plaintext)]
77 ctr.XORKeyStream(ciphertextData, plaintext)
78
79 omacCiphertext := e.omacT(2, ciphertextData)
80
81 tag := out[len(plaintext):]
82 for i := 0; i < e.tagSize; i++ {
83 tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i]
84 }
85 return ret
86}
87
88func (e *eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
89 if len(nonce) > e.nonceSize {
90 panic("crypto/eax: Nonce too long for this instance")
91 }
92 if len(ciphertext) < e.tagSize {
93 return nil, eaxError("Ciphertext shorter than tag length")
94 }
95 sep := len(ciphertext) - e.tagSize
96
97 // Compute tag
98 omacNonce := e.omacT(0, nonce)
99 omacAdata := e.omacT(1, adata)
100 omacCiphertext := e.omacT(2, ciphertext[:sep])
101
102 tag := make([]byte, e.tagSize)
103 for i := 0; i < e.tagSize; i++ {
104 tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i]
105 }
106
107 // Compare tags
108 if subtle.ConstantTimeCompare(ciphertext[sep:], tag) != 1 {
109 return nil, eaxError("Tag authentication failed")
110 }
111
112 // Decrypt ciphertext
113 ret, out := byteutil.SliceForAppend(dst, len(ciphertext))
114 ctr := cipher.NewCTR(e.block, omacNonce)
115 ctr.XORKeyStream(out, ciphertext[:sep])
116
117 return ret[:sep], nil
118}
119
120// Tweakable OMAC - Calls OMAC_K([t]_n || plaintext)
121func (e *eax) omacT(t byte, plaintext []byte) []byte {
122 blockSize := e.block.BlockSize()
123 byteT := make([]byte, blockSize)
124 byteT[blockSize-1] = t
125 concat := append(byteT, plaintext...)
126 return e.omac(concat)
127}
128
129func (e *eax) omac(plaintext []byte) []byte {
130 blockSize := e.block.BlockSize()
131 // L ← E_K(0^n); B ← 2L; P ← 4L
132 L := make([]byte, blockSize)
133 e.block.Encrypt(L, L)
134 B := byteutil.GfnDouble(L)
135 P := byteutil.GfnDouble(B)
136
137 // CBC with IV = 0
138 cbc := cipher.NewCBCEncrypter(e.block, make([]byte, blockSize))
139 padded := e.pad(plaintext, B, P)
140 cbcCiphertext := make([]byte, len(padded))
141 cbc.CryptBlocks(cbcCiphertext, padded)
142
143 return cbcCiphertext[len(cbcCiphertext)-blockSize:]
144}
145
146func (e *eax) pad(plaintext, B, P []byte) []byte {
147 // if |M| in {n, 2n, 3n, ...}
148 blockSize := e.block.BlockSize()
149 if len(plaintext) != 0 && len(plaintext)%blockSize == 0 {
150 return byteutil.RightXor(plaintext, B)
151 }
152
153 // else return (M || 1 || 0^(n−1−(|M| % n))) xor→ P
154 ending := make([]byte, blockSize-len(plaintext)%blockSize)
155 ending[0] = 0x80
156 padded := append(plaintext, ending...)
157 return byteutil.RightXor(padded, P)
158}
159
160func eaxError(err string) error {
161 return errors.New("crypto/eax: " + err)
162}