148 lines
3.6 KiB
Go
148 lines
3.6 KiB
Go
package cbcmac
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/cipher"
|
|
"errors"
|
|
|
|
"xdx.jelly/xgcl/utils/padding"
|
|
)
|
|
|
|
// CalculateMAC 计算输入iv和data的cbc Mac.
|
|
// 备注:
|
|
// 1. data必须是block.BlockSize的整数倍
|
|
// 2. iv必须是block.BlockSize大小
|
|
// 3. 第一个分组,iv输入全0
|
|
// 4. 第i个分组调用后,iv值被更新, 作为第i+1次分组的输入iv
|
|
// 5. mac必须为block.BlockSize大小
|
|
func CalculateMAC(data []byte, iv []byte, block cipher.Block, mac []byte) error {
|
|
blockSize := block.BlockSize()
|
|
if len(data) == 0 || len(data)%blockSize != 0 || len(iv) != blockSize || len(mac) != blockSize {
|
|
return errors.New("input arguments invalid")
|
|
}
|
|
blockMode := cipher.NewCBCEncrypter(block, iv)
|
|
|
|
blockMode.CryptBlocks(mac, data[:blockSize])
|
|
data = data[blockSize:]
|
|
|
|
for len(data) > 0 {
|
|
blockMode.CryptBlocks(mac, data[:blockSize])
|
|
data = data[blockSize:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const maxChunk = 64
|
|
|
|
type CbcMac struct {
|
|
mac []byte
|
|
pad padding.Padding
|
|
block cipher.Block
|
|
blockMode cipher.BlockMode
|
|
x [maxChunk]byte
|
|
nx int
|
|
}
|
|
|
|
// pad 填充方式。忽略的话为不填充
|
|
func New(block cipher.Block, pad ...padding.Padding) (*CbcMac, error) {
|
|
var p padding.Padding
|
|
if len(pad) > 0 {
|
|
p = pad[0]
|
|
} else {
|
|
p = padding.NonePadding{}
|
|
}
|
|
return &CbcMac{
|
|
pad: p,
|
|
block: block,
|
|
blockMode: cipher.NewCBCEncrypter(block, make([]byte, block.BlockSize())),
|
|
mac: make([]byte, block.BlockSize()),
|
|
}, nil
|
|
}
|
|
|
|
// ComputeMAC computes message authentication code (MAC) for the given data.
|
|
// data must paded
|
|
func (c *CbcMac) ComputeMAC(data []byte) ([]byte, error) {
|
|
c.Reset()
|
|
_, err := c.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer c.Reset()
|
|
return c.Mac([]byte{})
|
|
}
|
|
|
|
// VerifyMAC verifies whether the given MAC is a correct message authentication
|
|
// code (MAC) the given data.
|
|
func (c *CbcMac) VerifyMAC(mac []byte, data []byte) error {
|
|
c.Reset()
|
|
|
|
mac2, err := c.ComputeMAC(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes.Compare(mac2, mac) != 0 {
|
|
return errors.New("Mac verify failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *CbcMac) Write(p []byte) (n int, err error) {
|
|
blockSize := c.block.BlockSize()
|
|
|
|
if len(p)+c.nx < blockSize {
|
|
copy(c.x[c.nx:], p)
|
|
c.nx += len(p)
|
|
} else {
|
|
n = copy(c.x[c.nx:blockSize], p)
|
|
p = p[n:]
|
|
c.blockMode.CryptBlocks(c.mac, c.x[:blockSize])
|
|
|
|
for len(p) >= blockSize {
|
|
c.blockMode.CryptBlocks(c.mac, p[:blockSize])
|
|
p = p[blockSize:]
|
|
}
|
|
c.nx = copy(c.x[:], p)
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
// Mac appends the current mac to b and returns the resulting slice.
|
|
// tail is the un-computed data c.x[:c.nx].
|
|
func (c *CbcMac) Mac(b []byte) ([]byte, error) {
|
|
x := c.pad.Pad(c.x[:c.nx], c.block.BlockSize())
|
|
if len(x)%c.block.BlockSize() != 0 {
|
|
return nil, errors.New("invalid data length with no padding")
|
|
}
|
|
c.blockMode.CryptBlocks(c.mac, x)
|
|
return append(b, c.mac...), nil
|
|
}
|
|
|
|
// Sum is used to compute the mac but return nil while an error occur.
|
|
// It's prefered to use Mac instead.
|
|
func (c *CbcMac) Sum(b []byte) []byte {
|
|
m, err := c.Mac(b)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return m
|
|
}
|
|
|
|
// Reset resets the Hash to its initial state.
|
|
func (c *CbcMac) Reset() {
|
|
c.mac = make([]byte, c.block.BlockSize())
|
|
c.blockMode = cipher.NewCBCEncrypter(c.block, c.mac)
|
|
}
|
|
|
|
// Size returns the number of bytes Sum will return.
|
|
func (c *CbcMac) Size() int {
|
|
return c.block.BlockSize()
|
|
}
|
|
|
|
// BlockSize returns the hash's underlying block size.
|
|
// The Write method must be able to accept any amount
|
|
// of data, but it may operate more efficiently if all writes
|
|
// are a multiple of the block size.
|
|
func (c *CbcMac) BlockSize() int {
|
|
return c.block.BlockSize()
|
|
}
|