169 lines
4.3 KiB
Go
169 lines
4.3 KiB
Go
package blockmode
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/cipher"
|
|
|
|
"xdx.jelly/xgcl/gerrors"
|
|
"xdx.jelly/xgcl/internal/xor"
|
|
)
|
|
|
|
type ccm struct {
|
|
b EcbCbcBlockMode
|
|
nonceSize int
|
|
tagSize int
|
|
}
|
|
|
|
const ccmStandardNonceSize = 12
|
|
const ccmTagSize = 16
|
|
const ccmBlockSize = 16
|
|
|
|
// NewCCM 返回AEAD实例,其中nonce为12字节,tag为16字节
|
|
func NewCCM(cipher EcbCbcBlockMode) (cipher.AEAD, error) {
|
|
return NewCCMWithNonceAndTagSize(cipher, ccmStandardNonceSize, ccmTagSize)
|
|
}
|
|
|
|
// NewCCMWithNonceAndTagSize 返回一个AEAD接口对象,其中tagSize必须取值为4, 6, 8, 10, 12, 14, 16。
|
|
// nonceSize取值为{7,8,9,10,11,12,13}
|
|
//
|
|
// CCM不建议支持Init/Update/Final调用。需一次性将additional data 和plaintext/ciphertext传入。
|
|
func NewCCMWithNonceAndTagSize(cipher EcbCbcBlockMode, nonceSize, tagSize int) (cipher.AEAD, error) {
|
|
if nonceSize < 7 || nonceSize > 13 {
|
|
return nil, gerrors.WithAnnotating(ErrInvalidInput, "nonce size must be 7, 8, 9, 10, 11, 12 or 13")
|
|
}
|
|
if tagSize != 4 && tagSize != 6 && tagSize != 8 && tagSize != 10 && tagSize != 12 && tagSize != 14 && tagSize != 16 {
|
|
return nil, gerrors.WithAnnotating(ErrInvalidInput, "tag size must be 4, 6, 8, 10, 12, 14 or 16")
|
|
}
|
|
return &ccm{
|
|
b: cipher,
|
|
nonceSize: nonceSize,
|
|
tagSize: tagSize,
|
|
}, nil
|
|
}
|
|
|
|
// NonceSize 返回需要的nonce长度
|
|
func (c *ccm) NonceSize() int {
|
|
return c.nonceSize
|
|
}
|
|
|
|
func (c *ccm) Overhead() int {
|
|
return c.tagSize
|
|
}
|
|
|
|
func (c *ccm) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
|
B := format(nonce, additionalData, plaintext, c.tagSize)
|
|
|
|
iv := make([]byte, ccmBlockSize)
|
|
_ = c.b.CbcEncCryptBlocks(B, iv, B)
|
|
|
|
tagMask := make([]byte, c.tagSize)
|
|
copy(tagMask, B[len(B)-ccmBlockSize:])
|
|
|
|
N := c.deriveCounter(B[:0], nonce, len(plaintext))
|
|
_ = c.b.EcbEncCryptBlocks(N, N)
|
|
p := xor.XorBytes(N, N, plaintext)
|
|
tag := N[p : p+c.tagSize]
|
|
xor.XorBytes(tag, tagMask, N[len(N)-ccmBlockSize:])
|
|
return append(dst, N[:p+c.tagSize]...)
|
|
}
|
|
|
|
func (c *ccm) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
|
p := len(ciphertext) - c.Overhead()
|
|
ctrs := c.deriveCounter(nil, nonce, p)
|
|
_ = c.b.EcbEncCryptBlocks(ctrs, ctrs)
|
|
ret, plaintext := sliceForAppend(dst, p)
|
|
xor.XorBytes(plaintext, ciphertext[:p], ctrs[:p])
|
|
y0 := ctrs[len(ctrs)-ccmBlockSize:]
|
|
T := make([]byte, c.tagSize)
|
|
xor.XorBytes(T, y0, ciphertext[p:])
|
|
|
|
B := format(nonce, additionalData, plaintext, c.tagSize)
|
|
iv := make([]byte, ccmBlockSize)
|
|
_ = c.b.CbcEncCryptBlocks(B, iv, B)
|
|
|
|
if !bytes.Equal(T, B[len(B)-ccmBlockSize:len(B)-ccmBlockSize+c.tagSize]) {
|
|
return dst, gerrors.WithAnnotating(ErrAEADTagCheckFailed, "CCM tag check failed")
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
func format(nonce, ad, plain []byte, t int) []byte {
|
|
n := len(nonce)
|
|
a := uint64(len(ad))
|
|
p := len(plain)
|
|
q := 15 - n
|
|
|
|
B := make([]byte, 16)
|
|
if a > 0 {
|
|
B[0] |= 0x40
|
|
}
|
|
B[0] |= ((byte(t>>1 - 1)) << 3) | byte(q-1)
|
|
copy(B[1:], nonce)
|
|
Q := B[1+n:]
|
|
for i := q - 1; i >= 0; i-- {
|
|
Q[i] = byte(p)
|
|
p >>= 8
|
|
}
|
|
// p = len(plain)
|
|
|
|
if a > 0 {
|
|
if a < (1<<16)-(1<<8) {
|
|
B = append(B, byte(a>>8))
|
|
B = append(B, byte(a))
|
|
} else if a < (1 << 32) {
|
|
B = append(B, []byte{0xff, 0xfe,
|
|
byte(a >> 24), byte(a >> 16), byte(a >> 8), byte(a >> 0)}...)
|
|
} else {
|
|
B = append(B, []byte{0xff, 0xff,
|
|
byte(a >> 56), byte(a >> 48), byte(a >> 40), byte(a >> 32),
|
|
byte(a >> 24), byte(a >> 16), byte(a >> 8), byte(a >> 0)}...)
|
|
}
|
|
}
|
|
B = append(B, ad...)
|
|
paddingLen := ((len(B) + 15) >> 4) << 4
|
|
for i := len(B); i < paddingLen; i++ {
|
|
B = append(B, 0)
|
|
}
|
|
B = append(B, plain...)
|
|
paddingLen = ((len(B) + 15) >> 4) << 4
|
|
for i := len(B); i < paddingLen; i++ {
|
|
B = append(B, 0)
|
|
}
|
|
return B
|
|
}
|
|
|
|
func (c *ccm) deriveCounter(counterBuf []byte, nonce []byte, p int) []byte {
|
|
m := (p + ccmBlockSize - 1) >> 4
|
|
n := len(nonce)
|
|
q := 15 - n
|
|
if cap(counterBuf) < (m+1)*ccmBlockSize {
|
|
counterBuf = make([]byte, (m+1)*ccmBlockSize)
|
|
}
|
|
ret := counterBuf[:(m+1)*ccmBlockSize]
|
|
N := counterBuf[:m*ccmBlockSize]
|
|
N0 := counterBuf[m*ccmBlockSize : (m+1)*ccmBlockSize]
|
|
N0[0] = byte(q - 1)
|
|
copy(N0[1:], nonce)
|
|
for i := 1 + n; i < ccmBlockSize; i++ {
|
|
N0[i] = 0
|
|
}
|
|
N0[ccmBlockSize-1] = 1
|
|
|
|
for i := 0; i < m; i++ {
|
|
copy(N[i*ccmBlockSize:], N0)
|
|
for i := ccmBlockSize - 1; i >= 0; i-- {
|
|
N0[i]++
|
|
if N0[i] != 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := ccmBlockSize - 1; i >= ccmBlockSize-q; i-- {
|
|
N0[i] = 0
|
|
}
|
|
return ret
|
|
}
|