gmsm/cbcmac/cbcmac.go
2025-03-21 08:38:59 +08:00

498 lines
13 KiB
Go

// Copyright 2024 Sun Yimin. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package cbcmac implements the Message Authentication Code with the block chipher mechanisms.
package cbcmac
import (
"crypto/cipher"
"crypto/subtle"
"github.com/emmansun/gmsm/padding"
)
// Reference: GB/T 15821.1-2020 Security techniques
// Message authentication codes - Part 1: Mechanisms using block ciphers
// BlockCipherMAC is the interface that wraps the basic MAC method.
type BlockCipherMAC interface {
// Size returns the MAC value's number of bytes.
Size() int
// MAC calculates the MAC of the given data.
// The MAC value's number of bytes is returned by Size.
// Intercept message authentication code as needed.
MAC(src []byte) []byte
}
// cbcmac implements the basic CBC-MAC mode of operation for block ciphers.
type cbcmac struct {
b cipher.Block
pad padding.Padding
size int
}
// NewCBCMAC returns a CBC-MAC (GB/T 15821.1-2020 MAC scheme 1) instance that
// implements the MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewCBCMAC(b cipher.Block, size int) BlockCipherMAC {
return NewCBCMACWithPadding(b, size, padding.NewISO9797M2Padding)
}
// NewCBCMACWithPadding creates a new CBC-MAC (Cipher Block Chaining Message Authentication Code)
// with the specified block cipher, MAC size, and padding function. The MAC size must be greater
// than 0 and less than or equal to the block size of the cipher. If the size is invalid, the
// function will panic. The padding function is used to pad the input to the block size of the cipher.
func NewCBCMACWithPadding(b cipher.Block, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
return &cbcmac{b: b, pad: newPaddingFunc(uint(b.BlockSize())), size: size}
}
func (c *cbcmac) Size() int {
return c.size
}
// MAC calculates the MAC of the given data.
// The data is padded with the padding scheme of the block cipher before processing.
func (c *cbcmac) MAC(src []byte) []byte {
src = c.pad.Pad(src)
blockSize := c.b.BlockSize()
tag := make([]byte, blockSize)
for len(src) > 0 {
subtle.XORBytes(tag, tag, src[:blockSize])
c.b.Encrypt(tag, tag)
src = src[blockSize:]
}
return tag[:c.size]
}
// emac implements the EMAC mode of operation for block ciphers.
type emac struct {
pad padding.Padding
b1, b2 cipher.Block
size int
}
// NewEMAC returns an EMAC (GB/T 15821.1-2020 MAC scheme 2) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewEMAC(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
return NewEMACWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}
// NewEMACWithPadding creates a new instance of EMAC (Encrypted Message Authentication Code) with padding.
func NewEMACWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
var b1, b2 cipher.Block
var err error
if b1, err = creator(key1); err != nil {
panic(err)
}
if size <= 0 || size > b1.BlockSize() {
panic("cbcmac: invalid size")
}
if b2, err = creator(key2); err != nil {
panic(err)
}
return &emac{pad: newPaddingFunc(uint(b1.BlockSize())), b1: b1, b2: b2, size: size}
}
func (e *emac) Size() int {
return e.size
}
func (e *emac) MAC(src []byte) []byte {
src = e.pad.Pad(src)
blockSize := e.b1.BlockSize()
tag := make([]byte, blockSize)
for len(src) > 0 {
subtle.XORBytes(tag, tag, src[:blockSize])
e.b1.Encrypt(tag, tag)
src = src[blockSize:]
}
e.b2.Encrypt(tag, tag)
return tag[:e.size]
}
type ansiRetailMAC emac
// NewANSIRetailMAC returns an ANSI Retail MAC (GB/T 15821.1-2020 MAC scheme 3) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewANSIRetailMAC(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
return NewANSIRetailMACWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}
// NewANSIRetailMACWithPadding creates a new ANSI Retail MAC with padding.
func NewANSIRetailMACWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
return (*ansiRetailMAC)(NewEMACWithPadding(creator, key1, key2, size, newPaddingFunc).(*emac))
}
func (e *ansiRetailMAC) Size() int {
return e.size
}
func (e *ansiRetailMAC) MAC(src []byte) []byte {
src = e.pad.Pad(src)
blockSize := e.b1.BlockSize()
tag := make([]byte, blockSize)
for len(src) > 0 {
subtle.XORBytes(tag, tag, src[:blockSize])
e.b1.Encrypt(tag, tag)
src = src[blockSize:]
}
e.b2.Decrypt(tag, tag)
e.b1.Encrypt(tag, tag)
return tag[:e.size]
}
type macDES struct {
pad padding.Padding
b1, b2, b3 cipher.Block
size int
}
// NewMACDES returns a MAC-DES (GB/T 15821.1-2020 MAC scheme 4) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewMACDES(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int) BlockCipherMAC {
return NewMACDESWithPadding(creator, key1, key2, size, padding.NewISO9797M2Padding)
}
// NewMACDESWithPadding creates a new BlockCipherMAC using DES encryption with padding.
func NewMACDESWithPadding(creator func(key []byte) (cipher.Block, error), key1, key2 []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
var b1, b2, b3 cipher.Block
var err error
if b1, err = creator(key1); err != nil {
panic(err)
}
if size <= 0 || size > b1.BlockSize() {
panic("cbcmac: invalid size")
}
if b2, err = creator(key2); err != nil {
panic(err)
}
key3 := make([]byte, len(key2))
copy(key3, key2)
for i := range key3 {
key3[i] ^= 0xF0
}
if b3, err = creator(key3); err != nil {
panic(err)
}
return &macDES{pad: newPaddingFunc(uint(b1.BlockSize())), b1: b1, b2: b2, b3: b3, size: size}
}
func (m *macDES) Size() int {
return m.size
}
func (m *macDES) MAC(src []byte) []byte {
src = m.pad.Pad(src)
blockSize := m.b1.BlockSize()
tag := make([]byte, blockSize)
copy(tag, src[:blockSize])
m.b1.Encrypt(tag, tag)
m.b3.Encrypt(tag, tag)
src = src[blockSize:]
for len(src) > 0 {
subtle.XORBytes(tag, tag, src[:blockSize])
m.b1.Encrypt(tag, tag)
src = src[blockSize:]
}
m.b2.Encrypt(tag, tag)
return tag[:m.size]
}
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 (GB/T 15821.1-2020 MAC scheme 5) instance that implements MAC with the given block cipher.
//
// Reference: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38B.pdf
func NewCMAC(b cipher.Block, size int) *cmac {
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
blockSize := b.BlockSize()
k1 := make([]byte, blockSize)
k2 := make([]byte, blockSize)
b.Encrypt(k1, k1)
msb := shiftLeft(k1)
k1[len(k1)-1] ^= msb * 0b10000111
copy(k2, k1)
msb = shiftLeft(k2)
k2[len(k2)-1] ^= msb * 0b10000111
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 (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(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).
func shiftLeft(x []byte) byte {
var msb byte
for i := len(x) - 1; i >= 0; i-- {
msb, x[i] = x[i]>>7, x[i]<<1|msb
}
return msb
}
type lmac struct {
b1, b2 cipher.Block
pad padding.Padding
size int
}
// NewLMAC returns an LMAC (GB/T 15821.1-2020 MAC scheme 6) instance that
// implements MAC with the given block cipher. The padding scheme is ISO/IEC 9797-1 method 2.
func NewLMAC(creator func(key []byte) (cipher.Block, error), key []byte, size int) BlockCipherMAC {
return NewLMACWithPadding(creator, key, size, padding.NewISO9797M2Padding)
}
// NewLMACWithPadding creates a new LMAC (Length-based Message Authentication Code) with padding.
func NewLMACWithPadding(creator func(key []byte) (cipher.Block, error), key []byte, size int, newPaddingFunc padding.NewPaddingFunc) BlockCipherMAC {
var b, b1, b2 cipher.Block
var err error
if b, err = creator(key); err != nil {
panic(err)
}
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
blockSize := b.BlockSize()
key1 := make([]byte, blockSize)
key1[blockSize-1] = 0x01
key2 := make([]byte, blockSize)
key2[blockSize-1] = 0x02
b.Encrypt(key1, key1)
b.Encrypt(key2, key2)
if b1, err = creator(key1); err != nil {
panic(err)
}
if b2, err = creator(key2); err != nil {
panic(err)
}
return &lmac{b1: b1, b2: b2, pad: newPaddingFunc(uint(blockSize)), size: size}
}
func (l *lmac) Size() int {
return l.b1.BlockSize()
}
func (l *lmac) MAC(src []byte) []byte {
src = l.pad.Pad(src)
blockSize := l.b1.BlockSize()
tag := make([]byte, blockSize)
for len(src) > blockSize {
subtle.XORBytes(tag, tag, src[:blockSize])
l.b1.Encrypt(tag, tag)
src = src[blockSize:]
}
subtle.XORBytes(tag, tag, src[:blockSize])
l.b2.Encrypt(tag, tag)
return tag
}
type trCBCMAC struct {
b cipher.Block
size int
}
// NewTRCBCMAC returns a TR-CBC-MAC (GB/T 15821.1-2020 MAC scheme 7) instance that
// implements MAC with the given block cipher.
//
// Reference: TrCBC: Another look at CBC-MAC.
func NewTRCBCMAC(b cipher.Block, size int) BlockCipherMAC {
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
return &trCBCMAC{b: b, size: size}
}
func (t *trCBCMAC) Size() int {
return t.size
}
func (t *trCBCMAC) MAC(src []byte) []byte {
blockSize := t.b.BlockSize()
tag := make([]byte, blockSize)
padded := false
if len(src) == 0 || len(src)%blockSize != 0 {
pad := padding.NewISO9797M2Padding(uint(blockSize))
src = pad.Pad(src)
padded = true
}
for len(src) > 0 {
subtle.XORBytes(tag, tag, src[:blockSize])
t.b.Encrypt(tag, tag)
src = src[blockSize:]
}
if padded {
return tag[blockSize-t.size:]
}
return tag[:t.size]
}
type cbcrMAC struct {
b cipher.Block
size int
}
// NewCBCRMAC returns a CBCRMAC (GB/T 15821.1-2020 MAC scheme 8) instance that implements MAC with the given block cipher.
//
// Reference: CBCR: CBC MAC with rotating transformations.
func NewCBCRMAC(b cipher.Block, size int) BlockCipherMAC {
if size <= 0 || size > b.BlockSize() {
panic("cbcmac: invalid size")
}
return &cbcrMAC{b: b, size: size}
}
func (c *cbcrMAC) Size() int {
return c.size
}
func (c *cbcrMAC) MAC(src []byte) []byte {
blockSize := c.b.BlockSize()
tag := make([]byte, blockSize)
c.b.Encrypt(tag, tag)
padded := false
if len(src) == 0 || len(src)%blockSize != 0 {
pad := padding.NewISO9797M2Padding(uint(blockSize))
src = pad.Pad(src)
padded = true
}
for len(src) > blockSize {
subtle.XORBytes(tag, tag, src[:blockSize])
c.b.Encrypt(tag, tag)
src = src[blockSize:]
}
subtle.XORBytes(tag, tag, src[:blockSize])
if padded {
shiftLeft(tag)
} else {
shiftRight(tag)
}
c.b.Encrypt(tag, tag)
return tag[:c.size]
}
func shiftRight(x []byte) {
var lsb byte
for i := 0; i < len(x); i++ {
lsb, x[i] = x[i]<<7, x[i]>>1|lsb
}
x[0] ^= lsb
}