// 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 } }