Skip to content

Commit

Permalink
cbcmac: implement hash interface for cmac
Browse files Browse the repository at this point in the history
  • Loading branch information
emmansun authored Dec 5, 2024
1 parent 379396b commit f644a48
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 29 deletions.
131 changes: 103 additions & 28 deletions cbcmac/cbcmac.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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).
Expand Down
47 changes: 46 additions & 1 deletion cbcmac/cbcmac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/aes"
"encoding/hex"
"hash"
"testing"

"github.com/emmansun/gmsm/internal/cryptotest"
Expand Down Expand Up @@ -438,7 +439,7 @@ var testVectors = []struct {
"f69f2445df4f9b17ad2b417be66c3710",
hash: "e1992190549f6ed5696a2c056c315410",
tagsize: 16,
},
},
{
key: "603deb1015ca71be2b73aef0857d7781" +
"1f352c073b6108d72d9810a30914dff4",
Expand Down Expand Up @@ -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)
}

0 comments on commit f644a48

Please sign in to comment.