mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-28 16:06:24 +00:00
318 lines
9.4 KiB
Go
318 lines
9.4 KiB
Go
|
// Copyright (C) 2019 ProtonTech AG
|
||
|
|
||
|
// Package ocb provides an implementation of the OCB (offset codebook) mode of
|
||
|
// operation, as described in RFC-7253 of the IRTF and in Rogaway, Bellare,
|
||
|
// Black and Krovetz - OCB: A BLOCK-CIPHER MODE OF OPERATION FOR EFFICIENT
|
||
|
// AUTHENTICATED ENCRYPTION (2003).
|
||
|
// Security considerations (from RFC-7253): A private key MUST NOT be used to
|
||
|
// encrypt more than 2^48 blocks. Tag length should be at least 12 bytes (a
|
||
|
// brute-force forging adversary succeeds after 2^{tag length} attempts). A
|
||
|
// single key SHOULD NOT be used to decrypt ciphertext with different tag
|
||
|
// lengths. Nonces need not be secret, but MUST NOT be reused.
|
||
|
// This package only supports underlying block ciphers with 128-bit blocks,
|
||
|
// such as AES-{128, 192, 256}, but may be extended to other sizes.
|
||
|
package ocb
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/subtle"
|
||
|
"errors"
|
||
|
"github.com/ProtonMail/go-crypto/internal/byteutil"
|
||
|
"math/bits"
|
||
|
)
|
||
|
|
||
|
type ocb struct {
|
||
|
block cipher.Block
|
||
|
tagSize int
|
||
|
nonceSize int
|
||
|
mask mask
|
||
|
// Optimized en/decrypt: For each nonce N used to en/decrypt, the 'Ktop'
|
||
|
// internal variable can be reused for en/decrypting with nonces sharing
|
||
|
// all but the last 6 bits with N. The prefix of the first nonce used to
|
||
|
// compute the new Ktop, and the Ktop value itself, are stored in
|
||
|
// reusableKtop. If using incremental nonces, this saves one block cipher
|
||
|
// call every 63 out of 64 OCB encryptions, and stores one nonce and one
|
||
|
// output of the block cipher in memory only.
|
||
|
reusableKtop reusableKtop
|
||
|
}
|
||
|
|
||
|
type mask struct {
|
||
|
// L_*, L_$, (L_i)_{i ∈ N}
|
||
|
lAst []byte
|
||
|
lDol []byte
|
||
|
L [][]byte
|
||
|
}
|
||
|
|
||
|
type reusableKtop struct {
|
||
|
noncePrefix []byte
|
||
|
Ktop []byte
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
defaultTagSize = 16
|
||
|
defaultNonceSize = 15
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
enc = iota
|
||
|
dec
|
||
|
)
|
||
|
|
||
|
func (o *ocb) NonceSize() int {
|
||
|
return o.nonceSize
|
||
|
}
|
||
|
|
||
|
func (o *ocb) Overhead() int {
|
||
|
return o.tagSize
|
||
|
}
|
||
|
|
||
|
// NewOCB returns an OCB instance with the given block cipher and default
|
||
|
// tag and nonce sizes.
|
||
|
func NewOCB(block cipher.Block) (cipher.AEAD, error) {
|
||
|
return NewOCBWithNonceAndTagSize(block, defaultNonceSize, defaultTagSize)
|
||
|
}
|
||
|
|
||
|
// NewOCBWithNonceAndTagSize returns an OCB instance with the given block
|
||
|
// cipher, nonce length, and tag length. Panics on zero nonceSize and
|
||
|
// exceedingly long tag size.
|
||
|
//
|
||
|
// It is recommended to use at least 12 bytes as tag length.
|
||
|
func NewOCBWithNonceAndTagSize(
|
||
|
block cipher.Block, nonceSize, tagSize int) (cipher.AEAD, error) {
|
||
|
if block.BlockSize() != 16 {
|
||
|
return nil, ocbError("Block cipher must have 128-bit blocks")
|
||
|
}
|
||
|
if nonceSize < 1 {
|
||
|
return nil, ocbError("Incorrect nonce length")
|
||
|
}
|
||
|
if nonceSize >= block.BlockSize() {
|
||
|
return nil, ocbError("Nonce length exceeds blocksize - 1")
|
||
|
}
|
||
|
if tagSize > block.BlockSize() {
|
||
|
return nil, ocbError("Custom tag length exceeds blocksize")
|
||
|
}
|
||
|
return &ocb{
|
||
|
block: block,
|
||
|
tagSize: tagSize,
|
||
|
nonceSize: nonceSize,
|
||
|
mask: initializeMaskTable(block),
|
||
|
reusableKtop: reusableKtop{
|
||
|
noncePrefix: nil,
|
||
|
Ktop: nil,
|
||
|
},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (o *ocb) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
||
|
if len(nonce) > o.nonceSize {
|
||
|
panic("crypto/ocb: Incorrect nonce length given to OCB")
|
||
|
}
|
||
|
ret, out := byteutil.SliceForAppend(dst, len(plaintext)+o.tagSize)
|
||
|
o.crypt(enc, out, nonce, adata, plaintext)
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func (o *ocb) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
||
|
if len(nonce) > o.nonceSize {
|
||
|
panic("Nonce too long for this instance")
|
||
|
}
|
||
|
if len(ciphertext) < o.tagSize {
|
||
|
return nil, ocbError("Ciphertext shorter than tag length")
|
||
|
}
|
||
|
sep := len(ciphertext) - o.tagSize
|
||
|
ret, out := byteutil.SliceForAppend(dst, len(ciphertext))
|
||
|
ciphertextData := ciphertext[:sep]
|
||
|
tag := ciphertext[sep:]
|
||
|
o.crypt(dec, out, nonce, adata, ciphertextData)
|
||
|
if subtle.ConstantTimeCompare(ret[sep:], tag) == 1 {
|
||
|
ret = ret[:sep]
|
||
|
return ret, nil
|
||
|
}
|
||
|
for i := range out {
|
||
|
out[i] = 0
|
||
|
}
|
||
|
return nil, ocbError("Tag authentication failed")
|
||
|
}
|
||
|
|
||
|
// On instruction enc (resp. dec), crypt is the encrypt (resp. decrypt)
|
||
|
// function. It returns the resulting plain/ciphertext with the tag appended.
|
||
|
func (o *ocb) crypt(instruction int, Y, nonce, adata, X []byte) []byte {
|
||
|
//
|
||
|
// Consider X as a sequence of 128-bit blocks
|
||
|
//
|
||
|
// Note: For encryption (resp. decryption), X is the plaintext (resp., the
|
||
|
// ciphertext without the tag).
|
||
|
blockSize := o.block.BlockSize()
|
||
|
|
||
|
//
|
||
|
// Nonce-dependent and per-encryption variables
|
||
|
//
|
||
|
// Zero out the last 6 bits of the nonce into truncatedNonce to see if Ktop
|
||
|
// is already computed.
|
||
|
truncatedNonce := make([]byte, len(nonce))
|
||
|
copy(truncatedNonce, nonce)
|
||
|
truncatedNonce[len(truncatedNonce)-1] &= 192
|
||
|
Ktop := make([]byte, blockSize)
|
||
|
if bytes.Equal(truncatedNonce, o.reusableKtop.noncePrefix) {
|
||
|
Ktop = o.reusableKtop.Ktop
|
||
|
} else {
|
||
|
// Nonce = num2str(TAGLEN mod 128, 7) || zeros(120 - bitlen(N)) || 1 || N
|
||
|
paddedNonce := append(make([]byte, blockSize-1-len(nonce)), 1)
|
||
|
paddedNonce = append(paddedNonce, truncatedNonce...)
|
||
|
paddedNonce[0] |= byte(((8 * o.tagSize) % (8 * blockSize)) << 1)
|
||
|
// Last 6 bits of paddedNonce are already zero. Encrypt into Ktop
|
||
|
paddedNonce[blockSize-1] &= 192
|
||
|
Ktop = paddedNonce
|
||
|
o.block.Encrypt(Ktop, Ktop)
|
||
|
o.reusableKtop.noncePrefix = truncatedNonce
|
||
|
o.reusableKtop.Ktop = Ktop
|
||
|
}
|
||
|
|
||
|
// Stretch = Ktop || ((lower half of Ktop) XOR (lower half of Ktop << 8))
|
||
|
xorHalves := make([]byte, blockSize/2)
|
||
|
byteutil.XorBytes(xorHalves, Ktop[:blockSize/2], Ktop[1:1+blockSize/2])
|
||
|
stretch := append(Ktop, xorHalves...)
|
||
|
bottom := int(nonce[len(nonce)-1] & 63)
|
||
|
offset := make([]byte, len(stretch))
|
||
|
byteutil.ShiftNBytesLeft(offset, stretch, bottom)
|
||
|
offset = offset[:blockSize]
|
||
|
|
||
|
//
|
||
|
// Process any whole blocks
|
||
|
//
|
||
|
// Note: For encryption Y is ciphertext || tag, for decryption Y is
|
||
|
// plaintext || tag.
|
||
|
checksum := make([]byte, blockSize)
|
||
|
m := len(X) / blockSize
|
||
|
for i := 0; i < m; i++ {
|
||
|
index := bits.TrailingZeros(uint(i + 1))
|
||
|
if len(o.mask.L)-1 < index {
|
||
|
o.mask.extendTable(index)
|
||
|
}
|
||
|
byteutil.XorBytesMut(offset, o.mask.L[bits.TrailingZeros(uint(i+1))])
|
||
|
blockX := X[i*blockSize : (i+1)*blockSize]
|
||
|
blockY := Y[i*blockSize : (i+1)*blockSize]
|
||
|
byteutil.XorBytes(blockY, blockX, offset)
|
||
|
switch instruction {
|
||
|
case enc:
|
||
|
o.block.Encrypt(blockY, blockY)
|
||
|
byteutil.XorBytesMut(blockY, offset)
|
||
|
byteutil.XorBytesMut(checksum, blockX)
|
||
|
case dec:
|
||
|
o.block.Decrypt(blockY, blockY)
|
||
|
byteutil.XorBytesMut(blockY, offset)
|
||
|
byteutil.XorBytesMut(checksum, blockY)
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// Process any final partial block and compute raw tag
|
||
|
//
|
||
|
tag := make([]byte, blockSize)
|
||
|
if len(X)%blockSize != 0 {
|
||
|
byteutil.XorBytesMut(offset, o.mask.lAst)
|
||
|
pad := make([]byte, blockSize)
|
||
|
o.block.Encrypt(pad, offset)
|
||
|
chunkX := X[blockSize*m:]
|
||
|
chunkY := Y[blockSize*m : len(X)]
|
||
|
byteutil.XorBytes(chunkY, chunkX, pad[:len(chunkX)])
|
||
|
// P_* || bit(1) || zeroes(127) - len(P_*)
|
||
|
switch instruction {
|
||
|
case enc:
|
||
|
paddedY := append(chunkX, byte(128))
|
||
|
paddedY = append(paddedY, make([]byte, blockSize-len(chunkX)-1)...)
|
||
|
byteutil.XorBytesMut(checksum, paddedY)
|
||
|
case dec:
|
||
|
paddedX := append(chunkY, byte(128))
|
||
|
paddedX = append(paddedX, make([]byte, blockSize-len(chunkY)-1)...)
|
||
|
byteutil.XorBytesMut(checksum, paddedX)
|
||
|
}
|
||
|
byteutil.XorBytes(tag, checksum, offset)
|
||
|
byteutil.XorBytesMut(tag, o.mask.lDol)
|
||
|
o.block.Encrypt(tag, tag)
|
||
|
byteutil.XorBytesMut(tag, o.hash(adata))
|
||
|
copy(Y[blockSize*m+len(chunkY):], tag[:o.tagSize])
|
||
|
} else {
|
||
|
byteutil.XorBytes(tag, checksum, offset)
|
||
|
byteutil.XorBytesMut(tag, o.mask.lDol)
|
||
|
o.block.Encrypt(tag, tag)
|
||
|
byteutil.XorBytesMut(tag, o.hash(adata))
|
||
|
copy(Y[blockSize*m:], tag[:o.tagSize])
|
||
|
}
|
||
|
return Y
|
||
|
}
|
||
|
|
||
|
// This hash function is used to compute the tag. Per design, on empty input it
|
||
|
// returns a slice of zeros, of the same length as the underlying block cipher
|
||
|
// block size.
|
||
|
func (o *ocb) hash(adata []byte) []byte {
|
||
|
//
|
||
|
// Consider A as a sequence of 128-bit blocks
|
||
|
//
|
||
|
A := make([]byte, len(adata))
|
||
|
copy(A, adata)
|
||
|
blockSize := o.block.BlockSize()
|
||
|
|
||
|
//
|
||
|
// Process any whole blocks
|
||
|
//
|
||
|
sum := make([]byte, blockSize)
|
||
|
offset := make([]byte, blockSize)
|
||
|
m := len(A) / blockSize
|
||
|
for i := 0; i < m; i++ {
|
||
|
chunk := A[blockSize*i : blockSize*(i+1)]
|
||
|
index := bits.TrailingZeros(uint(i + 1))
|
||
|
// If the mask table is too short
|
||
|
if len(o.mask.L)-1 < index {
|
||
|
o.mask.extendTable(index)
|
||
|
}
|
||
|
byteutil.XorBytesMut(offset, o.mask.L[index])
|
||
|
byteutil.XorBytesMut(chunk, offset)
|
||
|
o.block.Encrypt(chunk, chunk)
|
||
|
byteutil.XorBytesMut(sum, chunk)
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Process any final partial block; compute final hash value
|
||
|
//
|
||
|
if len(A)%blockSize != 0 {
|
||
|
byteutil.XorBytesMut(offset, o.mask.lAst)
|
||
|
// Pad block with 1 || 0 ^ 127 - bitlength(a)
|
||
|
ending := make([]byte, blockSize-len(A)%blockSize)
|
||
|
ending[0] = 0x80
|
||
|
encrypted := append(A[blockSize*m:], ending...)
|
||
|
byteutil.XorBytesMut(encrypted, offset)
|
||
|
o.block.Encrypt(encrypted, encrypted)
|
||
|
byteutil.XorBytesMut(sum, encrypted)
|
||
|
}
|
||
|
return sum
|
||
|
}
|
||
|
|
||
|
func initializeMaskTable(block cipher.Block) mask {
|
||
|
//
|
||
|
// Key-dependent variables
|
||
|
//
|
||
|
lAst := make([]byte, block.BlockSize())
|
||
|
block.Encrypt(lAst, lAst)
|
||
|
lDol := byteutil.GfnDouble(lAst)
|
||
|
L := make([][]byte, 1)
|
||
|
L[0] = byteutil.GfnDouble(lDol)
|
||
|
|
||
|
return mask{
|
||
|
lAst: lAst,
|
||
|
lDol: lDol,
|
||
|
L: L,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Extends the L array of mask m up to L[limit], with L[i] = GfnDouble(L[i-1])
|
||
|
func (m *mask) extendTable(limit int) {
|
||
|
for i := len(m.L); i <= limit; i++ {
|
||
|
m.L = append(m.L, byteutil.GfnDouble(m.L[i-1]))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ocbError(err string) error {
|
||
|
return errors.New("crypto/ocb: " + err)
|
||
|
}
|