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() }