main
Raw Download raw file
  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}