You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
4.6 KiB
162 lines
4.6 KiB
// Copyright (C) 2019 ProtonTech AG
|
|
|
|
// Package eax provides an implementation of the EAX
|
|
// (encrypt-authenticate-translate) mode of operation, as described in
|
|
// Bellare, Rogaway, and Wagner "THE EAX MODE OF OPERATION: A TWO-PASS
|
|
// AUTHENTICATED-ENCRYPTION SCHEME OPTIMIZED FOR SIMPLICITY AND EFFICIENCY."
|
|
// In FSE'04, volume 3017 of LNCS, 2004
|
|
package eax
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"github.com/ProtonMail/go-crypto/internal/byteutil"
|
|
)
|
|
|
|
const (
|
|
defaultTagSize = 16
|
|
defaultNonceSize = 16
|
|
)
|
|
|
|
type eax struct {
|
|
block cipher.Block // Only AES-{128, 192, 256} supported
|
|
tagSize int // At least 12 bytes recommended
|
|
nonceSize int
|
|
}
|
|
|
|
func (e *eax) NonceSize() int {
|
|
return e.nonceSize
|
|
}
|
|
|
|
func (e *eax) Overhead() int {
|
|
return e.tagSize
|
|
}
|
|
|
|
// NewEAX returns an EAX instance with AES-{KEYLENGTH} and default nonce and
|
|
// tag lengths. Supports {128, 192, 256}- bit key length.
|
|
func NewEAX(block cipher.Block) (cipher.AEAD, error) {
|
|
return NewEAXWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize)
|
|
}
|
|
|
|
// NewEAXWithNonceAndTagSize returns an EAX instance with AES-{keyLength} and
|
|
// given nonce and tag lengths in bytes. Panics on zero nonceSize and
|
|
// exceedingly long tags.
|
|
//
|
|
// It is recommended to use at least 12 bytes as tag length (see, for instance,
|
|
// NIST SP 800-38D).
|
|
//
|
|
// Only to be used for compatibility with existing cryptosystems with
|
|
// non-standard parameters. For all other cases, prefer NewEAX.
|
|
func NewEAXWithNonceAndTagSize(
|
|
block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) {
|
|
if nonceSize < 1 {
|
|
return nil, eaxError("Cannot initialize EAX with nonceSize = 0")
|
|
}
|
|
if tagSize > block.BlockSize() {
|
|
return nil, eaxError("Custom tag length exceeds blocksize")
|
|
}
|
|
return &eax{
|
|
block: block,
|
|
tagSize: tagSize,
|
|
nonceSize: nonceSize,
|
|
}, nil
|
|
}
|
|
|
|
func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
|
if len(nonce) > e.nonceSize {
|
|
panic("crypto/eax: Nonce too long for this instance")
|
|
}
|
|
ret, out := byteutil.SliceForAppend(dst, len(plaintext) + e.tagSize)
|
|
omacNonce := e.omacT(0, nonce)
|
|
omacAdata := e.omacT(1, adata)
|
|
|
|
// Encrypt message using CTR mode and omacNonce as IV
|
|
ctr := cipher.NewCTR(e.block, omacNonce)
|
|
ciphertextData := out[:len(plaintext)]
|
|
ctr.XORKeyStream(ciphertextData, plaintext)
|
|
|
|
omacCiphertext := e.omacT(2, ciphertextData)
|
|
|
|
tag := out[len(plaintext):]
|
|
for i := 0; i < e.tagSize; i++ {
|
|
tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i]
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (e* eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
|
if len(nonce) > e.nonceSize {
|
|
panic("crypto/eax: Nonce too long for this instance")
|
|
}
|
|
if len(ciphertext) < e.tagSize {
|
|
return nil, eaxError("Ciphertext shorter than tag length")
|
|
}
|
|
sep := len(ciphertext) - e.tagSize
|
|
|
|
// Compute tag
|
|
omacNonce := e.omacT(0, nonce)
|
|
omacAdata := e.omacT(1, adata)
|
|
omacCiphertext := e.omacT(2, ciphertext[:sep])
|
|
|
|
tag := make([]byte, e.tagSize)
|
|
for i := 0; i < e.tagSize; i++ {
|
|
tag[i] = omacCiphertext[i] ^ omacNonce[i] ^ omacAdata[i]
|
|
}
|
|
|
|
// Compare tags
|
|
if subtle.ConstantTimeCompare(ciphertext[sep:], tag) != 1 {
|
|
return nil, eaxError("Tag authentication failed")
|
|
}
|
|
|
|
// Decrypt ciphertext
|
|
ret, out := byteutil.SliceForAppend(dst, len(ciphertext))
|
|
ctr := cipher.NewCTR(e.block, omacNonce)
|
|
ctr.XORKeyStream(out, ciphertext[:sep])
|
|
|
|
return ret[:sep], nil
|
|
}
|
|
|
|
// Tweakable OMAC - Calls OMAC_K([t]_n || plaintext)
|
|
func (e *eax) omacT(t byte, plaintext []byte) []byte {
|
|
blockSize := e.block.BlockSize()
|
|
byteT := make([]byte, blockSize)
|
|
byteT[blockSize-1] = t
|
|
concat := append(byteT, plaintext...)
|
|
return e.omac(concat)
|
|
}
|
|
|
|
func (e *eax) omac(plaintext []byte) []byte {
|
|
blockSize := e.block.BlockSize()
|
|
// L ← E_K(0^n); B ← 2L; P ← 4L
|
|
L := make([]byte, blockSize)
|
|
e.block.Encrypt(L, L)
|
|
B := byteutil.GfnDouble(L)
|
|
P := byteutil.GfnDouble(B)
|
|
|
|
// CBC with IV = 0
|
|
cbc := cipher.NewCBCEncrypter(e.block, make([]byte, blockSize))
|
|
padded := e.pad(plaintext, B, P)
|
|
cbcCiphertext := make([]byte, len(padded))
|
|
cbc.CryptBlocks(cbcCiphertext, padded)
|
|
|
|
return cbcCiphertext[len(cbcCiphertext)-blockSize:]
|
|
}
|
|
|
|
func (e *eax) pad(plaintext, B, P []byte) []byte {
|
|
// if |M| in {n, 2n, 3n, ...}
|
|
blockSize := e.block.BlockSize()
|
|
if len(plaintext) != 0 && len(plaintext)%blockSize == 0 {
|
|
return byteutil.RightXor(plaintext, B)
|
|
}
|
|
|
|
// else return (M || 1 || 0^(n−1−(|M| % n))) xor→ P
|
|
ending := make([]byte, blockSize-len(plaintext)%blockSize)
|
|
ending[0] = 0x80
|
|
padded := append(plaintext, ending...)
|
|
return byteutil.RightXor(padded, P)
|
|
}
|
|
|
|
func eaxError(err string) error {
|
|
return errors.New("crypto/eax: " + err)
|
|
}
|
|
|