202 lines
4.6 KiB
Go
202 lines
4.6 KiB
Go
// PMAC message authentication code, defined in
|
||
// http://web.cs.ucdavis.edu/~rogaway/ocb/pmac.pdf
|
||
|
||
package pmac
|
||
|
||
import (
|
||
"crypto/cipher"
|
||
"crypto/subtle"
|
||
"hash"
|
||
"math/bits"
|
||
|
||
"github.com/miscreant/miscreant-go/block"
|
||
)
|
||
|
||
// Number of L blocks to precompute (i.e. µ in the PMAC paper)
|
||
// TODO: dynamically compute these as needed
|
||
const precomputedBlocks = 31
|
||
|
||
type pmac struct {
|
||
// c is the block cipher we're using (i.e. AES-128 or AES-256)
|
||
c cipher.Block
|
||
|
||
// l is defined as follows (quoted from the PMAC paper):
|
||
//
|
||
// Equation 1:
|
||
//
|
||
// a · x =
|
||
// a<<1 if firstbit(a)=0
|
||
// (a<<1) ⊕ 0¹²⁰10000111 if firstbit(a)=1
|
||
//
|
||
// Equation 2:
|
||
//
|
||
// a · x⁻¹ =
|
||
// a>>1 if lastbit(a)=0
|
||
// (a>>1) ⊕ 10¹²⁰1000011 if lastbit(a)=1
|
||
//
|
||
// Let L(0) ← L. For i ∈ [1..µ], compute L(i) ← L(i − 1) · x by
|
||
// Equation (1) using a shift and a conditional xor.
|
||
//
|
||
// Compute L(−1) ← L · x⁻¹ by Equation (2), using a shift and a
|
||
// conditional xor.
|
||
//
|
||
// Save the values L(−1), L(0), L(1), L(2), ..., L(µ) in a table.
|
||
// (Alternatively, [ed: as we have done in this codebase] defer computing
|
||
// some or all of these L(i) values until the value is actually needed.)
|
||
l [precomputedBlocks]block.Block
|
||
|
||
// lInv contains the multiplicative inverse (i.e. right shift) of the first
|
||
// l-value, computed as described above, and is XORed into the tag in the
|
||
// event the message length is a multiple of the block size
|
||
lInv block.Block
|
||
|
||
// digest contains the PMAC tag-in-progress
|
||
digest block.Block
|
||
|
||
// offset is a block specific tweak to the input message
|
||
offset block.Block
|
||
|
||
// buf contains a part of the input message, processed a block-at-a-time
|
||
buf block.Block
|
||
|
||
// pos marks the end of plaintext in the buf
|
||
pos uint
|
||
|
||
// ctr is the number of blocks we have MAC'd so far
|
||
ctr uint
|
||
|
||
// finished is set true when we are done processing a message, and forbids
|
||
// any subsequent writes until we reset the internal state
|
||
finished bool
|
||
}
|
||
|
||
// New creates a new PMAC instance using the given cipher
|
||
func New(c cipher.Block) hash.Hash {
|
||
if c.BlockSize() != block.Size {
|
||
panic("pmac: invalid cipher block size")
|
||
}
|
||
|
||
d := new(pmac)
|
||
d.c = c
|
||
|
||
var tmp block.Block
|
||
tmp.Encrypt(c)
|
||
|
||
for i := range d.l {
|
||
copy(d.l[i][:], tmp[:])
|
||
tmp.Dbl()
|
||
}
|
||
|
||
// Compute L(−1) ← L · x⁻¹:
|
||
//
|
||
// a>>1 if lastbit(a)=0
|
||
// (a>>1) ⊕ 10¹²⁰1000011 if lastbit(a)=1
|
||
//
|
||
copy(tmp[:], d.l[0][:])
|
||
lastBit := int(tmp[block.Size-1] & 0x01)
|
||
|
||
for i := block.Size - 1; i > 0; i-- {
|
||
carry := byte(subtle.ConstantTimeSelect(int(tmp[i-1]&1), 0x80, 0))
|
||
tmp[i] = (tmp[i] >> 1) | carry
|
||
}
|
||
|
||
tmp[0] >>= 1
|
||
tmp[0] ^= byte(subtle.ConstantTimeSelect(lastBit, 0x80, 0))
|
||
tmp[block.Size-1] ^= byte(subtle.ConstantTimeSelect(lastBit, block.R>>1, 0))
|
||
copy(d.lInv[:], tmp[:])
|
||
|
||
return d
|
||
}
|
||
|
||
// Reset clears the digest state, starting a new digest.
|
||
func (d *pmac) Reset() {
|
||
d.digest.Clear()
|
||
d.offset.Clear()
|
||
d.buf.Clear()
|
||
d.pos = 0
|
||
d.ctr = 0
|
||
d.finished = false
|
||
}
|
||
|
||
// Write adds the given data to the digest state.
|
||
func (d *pmac) Write(msg []byte) (int, error) {
|
||
if d.finished {
|
||
panic("pmac: already finished")
|
||
}
|
||
|
||
var msgPos, msgLen, remaining uint
|
||
msgLen = uint(len(msg))
|
||
remaining = block.Size - d.pos
|
||
|
||
// Finish filling the internal buf with the message
|
||
if msgLen > remaining {
|
||
copy(d.buf[d.pos:], msg[:remaining])
|
||
|
||
msgPos += remaining
|
||
msgLen -= remaining
|
||
|
||
d.processBuffer()
|
||
}
|
||
|
||
// So long as we have more than a blocks worth of data, compute
|
||
// whole-sized blocks at a time.
|
||
for msgLen > block.Size {
|
||
copy(d.buf[:], msg[msgPos:msgPos+block.Size])
|
||
|
||
msgPos += block.Size
|
||
msgLen -= block.Size
|
||
|
||
d.processBuffer()
|
||
}
|
||
|
||
if msgLen > 0 {
|
||
copy(d.buf[d.pos:d.pos+msgLen], msg[msgPos:])
|
||
d.pos += msgLen
|
||
}
|
||
|
||
return len(msg), nil
|
||
}
|
||
|
||
// Sum returns the PMAC digest, one cipher block in length,
|
||
// of the data written with Write.
|
||
func (d *pmac) Sum(in []byte) []byte {
|
||
if d.finished {
|
||
panic("pmac: already finished")
|
||
}
|
||
|
||
if d.pos == block.Size {
|
||
xor(d.digest[:], d.buf[:])
|
||
xor(d.digest[:], d.lInv[:])
|
||
} else {
|
||
xor(d.digest[:], d.buf[:d.pos])
|
||
d.digest[d.pos] ^= 0x80
|
||
}
|
||
|
||
d.digest.Encrypt(d.c)
|
||
d.finished = true
|
||
|
||
return append(in, d.digest[:]...)
|
||
}
|
||
|
||
func (d *pmac) Size() int { return block.Size }
|
||
|
||
func (d *pmac) BlockSize() int { return block.Size }
|
||
|
||
// Update the internal tag state based on the buf contents
|
||
func (d *pmac) processBuffer() {
|
||
xor(d.offset[:], d.l[bits.TrailingZeros(d.ctr+1)][:])
|
||
xor(d.buf[:], d.offset[:])
|
||
d.ctr++
|
||
|
||
d.buf.Encrypt(d.c)
|
||
xor(d.digest[:], d.buf[:])
|
||
d.pos = 0
|
||
}
|
||
|
||
// XOR the contents of b into a in-place
|
||
func xor(a, b []byte) {
|
||
for i, v := range b {
|
||
a[i] ^= v
|
||
}
|
||
}
|