From f644a483e34b96b5092f1cfd68894dff8e95a149 Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Thu, 5 Dec 2024 17:39:45 +0800 Subject: [PATCH] cbcmac: implement hash interface for cmac --- cbcmac/cbcmac.go | 131 +++++++++++++++++++++++++++++++++--------- cbcmac/cbcmac_test.go | 47 ++++++++++++++- 2 files changed, 149 insertions(+), 29 deletions(-) diff --git a/cbcmac/cbcmac.go b/cbcmac/cbcmac.go index 6dd7fa3..3b6b29b 100644 --- a/cbcmac/cbcmac.go +++ b/cbcmac/cbcmac.go @@ -184,16 +184,21 @@ func (m *macDES) MAC(src []byte) []byte { } type cmac struct { - b cipher.Block - k1, k2 []byte - size int + 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 { - // Special-cased as a single empty partial final block. - copy(tag, c.k2) - tag[len(src)] ^= 0b10000000 - c.b.Encrypt(tag, tag) - return tag +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 + } } - for len(src) >= blockSize { - subtle.XORBytes(tag, src[:blockSize], tag) - if len(src) == blockSize { - // Final complete block. - subtle.XORBytes(tag, c.k1, tag) + lenP := len(p) + if lenP > d.blockSize { + n := lenP &^ (d.blockSize - 1) + if n == lenP { + n -= d.blockSize } - c.b.Encrypt(tag, tag) - src = src[blockSize:] + d.block(p[:n]) + p = p[n:] } - 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) + // save remaining partial/full block + if len(p) > 0 { + d.nx = copy(d.x[:], p) } - return tag[:c.size] + 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(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 +} + +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). diff --git a/cbcmac/cbcmac_test.go b/cbcmac/cbcmac_test.go index 77b1fc7..cff5f25 100644 --- a/cbcmac/cbcmac_test.go +++ b/cbcmac/cbcmac_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/aes" "encoding/hex" + "hash" "testing" "github.com/emmansun/gmsm/internal/cryptotest" @@ -438,7 +439,7 @@ var testVectors = []struct { "f69f2445df4f9b17ad2b417be66c3710", hash: "e1992190549f6ed5696a2c056c315410", tagsize: 16, - }, + }, { key: "603deb1015ca71be2b73aef0857d7781" + "1f352c073b6108d72d9810a30914dff4", @@ -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) +}