cbcmac: implement hash interface for cmac

This commit is contained in:
Sun Yimin 2024-12-05 17:39:45 +08:00 committed by GitHub
parent 379396b688
commit f644a483e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 149 additions and 29 deletions

View File

@ -187,13 +187,18 @@ type cmac struct {
b cipher.Block
k1, k2 []byte
size int
blockSize int
tag []byte
x []byte
nx int
len uint64
}
// NewCMAC returns a CMAC instance that implements MAC with the given block cipher.
// GB/T 15821.1-2020 MAC scheme 5
//
// Reference: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38B.pdf
func NewCMAC(b cipher.Block, size int) BockCipherMAC {
func NewCMAC(b cipher.Block, size int) *cmac {
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
@ -208,40 +213,110 @@ func NewCMAC(b cipher.Block, size int) BockCipherMAC {
msb = shiftLeft(k2)
k2[len(k2)-1] ^= msb * 0b10000111
return &cmac{b: b, k1: k1, k2: k2, size: size}
d := &cmac{b: b, k1: k1, k2: k2, size: size}
d.blockSize = blockSize
d.tag = make([]byte, blockSize)
d.x = make([]byte, blockSize)
return d
}
func (c *cmac) Reset() {
for i := range c.tag {
c.tag[i] = 0
}
c.nx = 0
c.len = 0
}
func (c *cmac) BlockSize() int {
return c.blockSize
}
func (c *cmac) Size() int {
return c.size
}
func (c *cmac) MAC(src []byte) []byte {
blockSize := c.b.BlockSize()
tag := make([]byte, blockSize)
if len(src) == 0 {
func (d *cmac) Write(p []byte) (nn int, err error) {
nn = len(p)
if nn == 0 {
// nothing to do
return
}
d.len += uint64(nn)
if d.nx == d.blockSize {
// handle remaining full block
d.block(d.x)
d.nx = 0
} else if d.nx > 0 {
// handle remaining incomplete block
n := copy(d.x[d.nx:], p)
d.nx += n
p = p[n:]
if len(p) > 0 {
d.block(d.x)
d.nx = 0
}
}
lenP := len(p)
if lenP > d.blockSize {
n := lenP &^ (d.blockSize - 1)
if n == lenP {
n -= d.blockSize
}
d.block(p[:n])
p = p[n:]
}
// save remaining partial/full block
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (c *cmac) block(p []byte) {
for len(p) >= c.blockSize {
subtle.XORBytes(c.tag, p[:c.blockSize], c.tag)
c.b.Encrypt(c.tag, c.tag)
p = p[c.blockSize:]
}
}
// Sum appends the current hash to in and returns the resulting slice.
// It does not change the underlying hash state.
func (d *cmac) Sum(in []byte) []byte {
// Make a copy of d so that caller can keep writing and summing.
// shared block cipher and k1, k2, x
d0 := *d
// use slices.Clone() later
d0.tag = make([]byte, d.blockSize)
copy(d0.tag, d.tag)
hash := d0.checkSum()
return append(in, hash[:]...)
}
func (c *cmac) checkSum() []byte {
tag := make([]byte, c.size)
if c.nx == 0 {
// Special-cased as a single empty partial final block.
copy(tag, c.k2)
tag[len(src)] ^= 0b10000000
c.b.Encrypt(tag, tag)
copy(c.tag, c.k2)
c.tag[0] ^= 0b10000000
} else if c.nx == c.blockSize {
subtle.XORBytes(c.tag, c.x, c.tag)
subtle.XORBytes(c.tag, c.k1, c.tag)
} else {
subtle.XORBytes(c.tag, c.x, c.tag)
c.tag[c.nx] ^= 0b10000000
subtle.XORBytes(c.tag, c.k2, c.tag)
}
c.b.Encrypt(c.tag, c.tag)
copy(tag, c.tag[:c.size])
return tag
}
for len(src) >= blockSize {
subtle.XORBytes(tag, src[:blockSize], tag)
if len(src) == blockSize {
// Final complete block.
subtle.XORBytes(tag, c.k1, tag)
}
c.b.Encrypt(tag, tag)
src = src[blockSize:]
}
if len(src) > 0 {
// Final incomplete block.
subtle.XORBytes(tag, src, tag)
subtle.XORBytes(tag, c.k2, tag)
tag[len(src)] ^= 0b10000000
c.b.Encrypt(tag, tag)
}
return tag[:c.size]
}
func (c *cmac) MAC(src []byte) []byte {
c.Reset()
c.Write(src)
return c.Sum(nil)
}
// shiftLeft sets x to x << 1, and returns MSB₁(x).

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/aes"
"encoding/hex"
"hash"
"testing"
"github.com/emmansun/gmsm/internal/cryptotest"
@ -475,3 +476,47 @@ func TestCMACAES(t *testing.T) {
}
}
}
func TestCMACHash(t *testing.T) {
t.Run("CMAC Hash", func(t *testing.T) {
cryptotest.TestHash(t, func() hash.Hash {
key := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}
block, err := aes.NewCipher(key)
if err != nil {
t.Fatal(err)
}
return NewCMAC(block, 16)
})
})
}
var buf = make([]byte, 8192)
func benchmarkSize(hash hash.Hash, b *testing.B, size int) {
b.SetBytes(int64(size))
sum := make([]byte, hash.Size())
for i := 0; i < b.N; i++ {
hash.Reset()
hash.Write(buf[:size])
hash.Sum(sum[:0])
}
}
func BenchmarkAESCMAC1K(b *testing.B) {
key := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}
block, err := aes.NewCipher(key)
if err != nil {
b.Fatal(err)
}
benchmarkSize(NewCMAC(block, 16), b, 1024)
}
func BenchmarkSM4CMAC1K(b *testing.B) {
key := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}
block, err := sm4.NewCipher(key)
if err != nil {
b.Fatal(err)
}
benchmarkSize(NewCMAC(block, 16), b, 1024)
}