mirror of
https://github.com/emmansun/gmsm.git
synced 2025-10-14 15:20:45 +08:00
cipher: initial support gxm & mur modes
This commit is contained in:
parent
570040e243
commit
00a09d0208
126
cipher/ghash.go
Normal file
126
cipher/ghash.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import "github.com/emmansun/gmsm/internal/byteorder"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ghashBlockSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// ghashFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM
|
||||||
|
// standard and make binary.BigEndian suitable for marshaling these values, the
|
||||||
|
// bits are stored in big endian order. For example:
|
||||||
|
//
|
||||||
|
// the coefficient of x⁰ can be obtained by v.low >> 63.
|
||||||
|
// the coefficient of x⁶³ can be obtained by v.low & 1.
|
||||||
|
// the coefficient of x⁶⁴ can be obtained by v.high >> 63.
|
||||||
|
// the coefficient of x¹²⁷ can be obtained by v.high & 1.
|
||||||
|
type ghashFieldElement struct {
|
||||||
|
low, high uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverseBits reverses the order of the bits of 4-bit number in i.
|
||||||
|
func reverseBits(i int) int {
|
||||||
|
i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
|
||||||
|
i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// hctrAdd adds two elements of GF(2¹²⁸) and returns the sum.
|
||||||
|
func ghashAdd(x, y *ghashFieldElement) ghashFieldElement {
|
||||||
|
// Addition in a characteristic 2 field is just XOR.
|
||||||
|
return ghashFieldElement{x.low ^ y.low, x.high ^ y.high}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hctrDouble returns the result of doubling an element of GF(2¹²⁸).
|
||||||
|
func ghashDouble(x *ghashFieldElement) (double ghashFieldElement) {
|
||||||
|
msbSet := x.high&1 == 1
|
||||||
|
|
||||||
|
// Because of the bit-ordering, doubling is actually a right shift.
|
||||||
|
double.high = x.high >> 1
|
||||||
|
double.high |= x.low << 63
|
||||||
|
double.low = x.low >> 1
|
||||||
|
|
||||||
|
// If the most-significant bit was set before shifting then it,
|
||||||
|
// conceptually, becomes a term of x^128. This is greater than the
|
||||||
|
// irreducible polynomial so the result has to be reduced. The
|
||||||
|
// irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to
|
||||||
|
// eliminate the term at x^128 which also means subtracting the other
|
||||||
|
// four terms. In characteristic 2 fields, subtraction == addition ==
|
||||||
|
// XOR.
|
||||||
|
if msbSet {
|
||||||
|
double.low ^= 0xe100000000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ghashReductionTable is stored irreducible polynomial's double & add precomputed results.
|
||||||
|
// 0000 - 0
|
||||||
|
// 0001 - irreducible polynomial >> 3
|
||||||
|
// 0010 - irreducible polynomial >> 2
|
||||||
|
// 0011 - (irreducible polynomial >> 3 xor irreducible polynomial >> 2)
|
||||||
|
// ...
|
||||||
|
// 1000 - just the irreducible polynomial
|
||||||
|
var ghashReductionTable = []uint16{
|
||||||
|
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
|
||||||
|
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ghashMul sets y to y*H, where H is the GHASH key, fixed during New.
|
||||||
|
func ghashMul(productTable *[16]ghashFieldElement, y *ghashFieldElement) {
|
||||||
|
var z ghashFieldElement
|
||||||
|
|
||||||
|
// Eliminate bounds checks in the loop.
|
||||||
|
_ = ghashReductionTable[0xf]
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
word := y.high
|
||||||
|
if i == 1 {
|
||||||
|
word = y.low
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplication works by multiplying z by 16 and adding in
|
||||||
|
// one of the precomputed multiples of hash key.
|
||||||
|
for j := 0; j < 64; j += 4 {
|
||||||
|
msw := z.high & 0xf
|
||||||
|
z.high >>= 4
|
||||||
|
z.high |= z.low << 60
|
||||||
|
z.low >>= 4
|
||||||
|
z.low ^= uint64(ghashReductionTable[msw]) << 48
|
||||||
|
|
||||||
|
// the values in |table| are ordered for
|
||||||
|
// little-endian bit positions.
|
||||||
|
t := &productTable[word&0xf]
|
||||||
|
|
||||||
|
z.low ^= t.low
|
||||||
|
z.high ^= t.high
|
||||||
|
word >>= 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*y = z
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateBlocks extends y with more polynomial terms from blocks, based on
|
||||||
|
// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks.
|
||||||
|
func updateBlocks(productTable *[16]ghashFieldElement, y *ghashFieldElement, blocks []byte) {
|
||||||
|
for len(blocks) > 0 {
|
||||||
|
y.low ^= byteorder.BEUint64(blocks)
|
||||||
|
y.high ^= byteorder.BEUint64(blocks[8:])
|
||||||
|
ghashMul(productTable, y)
|
||||||
|
blocks = blocks[blockSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ghashUpdate extends y with more polynomial terms from data. If data is not a
|
||||||
|
// multiple of gcmBlockSize bytes long then the remainder is zero padded.
|
||||||
|
func ghashUpdate(productTable *[16]ghashFieldElement, y *ghashFieldElement, data []byte) {
|
||||||
|
fullBlocks := (len(data) >> 4) << 4
|
||||||
|
updateBlocks(productTable, y, data[:fullBlocks])
|
||||||
|
|
||||||
|
if len(data) != fullBlocks {
|
||||||
|
var partialBlock [blockSize]byte
|
||||||
|
copy(partialBlock[:], data[fullBlocks:])
|
||||||
|
updateBlocks(productTable, y, partialBlock[:])
|
||||||
|
}
|
||||||
|
}
|
143
cipher/gxm.go
Normal file
143
cipher/gxm.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/internal/alias"
|
||||||
|
"github.com/emmansun/gmsm/internal/byteorder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gxm struct {
|
||||||
|
stream cipher.Stream
|
||||||
|
tagSize int
|
||||||
|
tagMask [ghashBlockSize]byte
|
||||||
|
// productTable contains the first sixteen powers of the hash key.
|
||||||
|
// However, they are in bit reversed order.
|
||||||
|
productTable [16]ghashFieldElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGXM creates a new GXM instance using the provided cipher stream and hash key.
|
||||||
|
// It uses the default tag size of 16 bytes.
|
||||||
|
func NewGXM(stream cipher.Stream, hkey []byte) (*gxm, error) {
|
||||||
|
return NewGXMWithTagSize(stream, hkey, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGXMWithTagSize creates a new instance of GXM (Galois XOR Mode) with a specified tag size.
|
||||||
|
func NewGXMWithTagSize(stream cipher.Stream, hkey []byte, tagSize int) (*gxm, error) {
|
||||||
|
if len(hkey) != ghashBlockSize {
|
||||||
|
return nil, errors.New("cipher: invalid hash key length")
|
||||||
|
}
|
||||||
|
if tagSize < 8 || tagSize > 16 {
|
||||||
|
return nil, errors.New("cipher: invalid tag size")
|
||||||
|
}
|
||||||
|
c := &gxm{}
|
||||||
|
c.stream = stream
|
||||||
|
c.tagSize = tagSize
|
||||||
|
// We precompute 16 multiples of |key|. However, when we do lookups
|
||||||
|
// into this table we'll be using bits from a field element and
|
||||||
|
// therefore the bits will be in the reverse order. So normally one
|
||||||
|
// would expect, say, 4*key to be in index 4 of the table but due to
|
||||||
|
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
||||||
|
x := ghashFieldElement{
|
||||||
|
byteorder.BEUint64(hkey[:8]),
|
||||||
|
byteorder.BEUint64(hkey[8:blockSize]),
|
||||||
|
}
|
||||||
|
c.productTable[reverseBits(1)] = x
|
||||||
|
|
||||||
|
for i := 2; i < 16; i += 2 {
|
||||||
|
c.productTable[reverseBits(i)] = ghashDouble(&c.productTable[reverseBits(i/2)])
|
||||||
|
c.productTable[reverseBits(i+1)] = ghashAdd(&c.productTable[reverseBits(i)], &x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt zero block to get the tag mask
|
||||||
|
stream.XORKeyStream(c.tagMask[:tagSize], c.tagMask[:tagSize])
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overhead returns the maximum difference between the lengths of a
|
||||||
|
// plaintext and its ciphertext.
|
||||||
|
func (g *gxm) Overhead() int {
|
||||||
|
return g.tagSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal encrypts and authenticates plaintext, authenticates the
|
||||||
|
// additional data and appends the result to dst, returning the updated
|
||||||
|
// slice. The nonce must be NonceSize() bytes long and unique for all
|
||||||
|
// time, for a given key.
|
||||||
|
//
|
||||||
|
// To reuse plaintext's storage for the encrypted output, use plaintext[:0]
|
||||||
|
// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
|
||||||
|
// dst and additionalData may not overlap.
|
||||||
|
func (g *gxm) Seal(dst, plaintext, additionalData []byte) []byte {
|
||||||
|
ret, out := alias.SliceForAppend(dst, len(plaintext)+g.tagSize)
|
||||||
|
if alias.InexactOverlap(out, plaintext) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and additional data")
|
||||||
|
}
|
||||||
|
|
||||||
|
g.stream.XORKeyStream(out, plaintext)
|
||||||
|
g.gxmAuth(out[len(plaintext):], out[:len(plaintext)], additionalData)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open decrypts and authenticates ciphertext, authenticates the
|
||||||
|
// additional data and, if successful, appends the resulting plaintext
|
||||||
|
// to dst, returning the updated slice. The nonce must be NonceSize()
|
||||||
|
// bytes long and both it and the additional data must match the
|
||||||
|
// value passed to Seal.
|
||||||
|
//
|
||||||
|
// To reuse ciphertext's storage for the decrypted output, use ciphertext[:0]
|
||||||
|
// as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.
|
||||||
|
// dst and additionalData may not overlap.
|
||||||
|
//
|
||||||
|
// Even if the function fails, the contents of dst, up to its capacity,
|
||||||
|
// may be overwritten.
|
||||||
|
func (g *gxm) Open(dst, ciphertext, additionalData []byte) ([]byte, error) {
|
||||||
|
if len(ciphertext) < g.tagSize {
|
||||||
|
return nil, errOpen
|
||||||
|
}
|
||||||
|
ret, out := alias.SliceForAppend(dst, len(ciphertext)-g.tagSize)
|
||||||
|
if alias.InexactOverlap(out, ciphertext) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and additional data")
|
||||||
|
}
|
||||||
|
tag := ciphertext[len(ciphertext)-g.tagSize:]
|
||||||
|
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
|
||||||
|
|
||||||
|
var expectedTag [blockSize]byte
|
||||||
|
g.gxmAuth(expectedTag[:], ciphertext, additionalData)
|
||||||
|
|
||||||
|
// Use subtle.ConstantTimeCompare to avoid leaking timing information.
|
||||||
|
if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
|
||||||
|
// We sometimes decrypt and authenticate concurrently, so we overwrite
|
||||||
|
// dst in the event of a tag mismatch. To be consistent across platforms
|
||||||
|
// and to avoid releasing unauthenticated plaintext, we clear the buffer
|
||||||
|
// in the event of an error.
|
||||||
|
clear(out)
|
||||||
|
return nil, errOpen
|
||||||
|
}
|
||||||
|
g.stream.XORKeyStream(out, ciphertext)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gxm) gxmAuth(out, ciphertext, additionalData []byte) {
|
||||||
|
var tag [ghashBlockSize]byte
|
||||||
|
tagField := ghashFieldElement{}
|
||||||
|
ghashUpdate(&g.productTable, &tagField, additionalData)
|
||||||
|
ghashUpdate(&g.productTable, &tagField, ciphertext)
|
||||||
|
lenBlock := make([]byte, 16)
|
||||||
|
byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8)
|
||||||
|
byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8)
|
||||||
|
ghashUpdate(&g.productTable, &tagField, lenBlock)
|
||||||
|
byteorder.BEPutUint64(tag[:], tagField.low)
|
||||||
|
byteorder.BEPutUint64(tag[8:], tagField.high)
|
||||||
|
subtle.XORBytes(tag[:], tag[:], g.tagMask[:])
|
||||||
|
copy(out, tag[:g.tagSize])
|
||||||
|
}
|
107
cipher/hctr.go
107
cipher/hctr.go
@ -40,66 +40,6 @@ type LengthPreservingMode interface {
|
|||||||
BlockSize() int
|
BlockSize() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// hctrFieldElement represents a value in GF(2¹²⁸). In order to reflect the HCTR
|
|
||||||
// standard and make binary.BigEndian suitable for marshaling these values, the
|
|
||||||
// bits are stored in big endian order. For example:
|
|
||||||
//
|
|
||||||
// the coefficient of x⁰ can be obtained by v.low >> 63.
|
|
||||||
// the coefficient of x⁶³ can be obtained by v.low & 1.
|
|
||||||
// the coefficient of x⁶⁴ can be obtained by v.high >> 63.
|
|
||||||
// the coefficient of x¹²⁷ can be obtained by v.high & 1.
|
|
||||||
type hctrFieldElement struct {
|
|
||||||
low, high uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// reverseBits reverses the order of the bits of 4-bit number in i.
|
|
||||||
func reverseBits(i int) int {
|
|
||||||
i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
|
|
||||||
i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// hctrAdd adds two elements of GF(2¹²⁸) and returns the sum.
|
|
||||||
func hctrAdd(x, y *hctrFieldElement) hctrFieldElement {
|
|
||||||
// Addition in a characteristic 2 field is just XOR.
|
|
||||||
return hctrFieldElement{x.low ^ y.low, x.high ^ y.high}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hctrDouble returns the result of doubling an element of GF(2¹²⁸).
|
|
||||||
func hctrDouble(x *hctrFieldElement) (double hctrFieldElement) {
|
|
||||||
msbSet := x.high&1 == 1
|
|
||||||
|
|
||||||
// Because of the bit-ordering, doubling is actually a right shift.
|
|
||||||
double.high = x.high >> 1
|
|
||||||
double.high |= x.low << 63
|
|
||||||
double.low = x.low >> 1
|
|
||||||
|
|
||||||
// If the most-significant bit was set before shifting then it,
|
|
||||||
// conceptually, becomes a term of x^128. This is greater than the
|
|
||||||
// irreducible polynomial so the result has to be reduced. The
|
|
||||||
// irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to
|
|
||||||
// eliminate the term at x^128 which also means subtracting the other
|
|
||||||
// four terms. In characteristic 2 fields, subtraction == addition ==
|
|
||||||
// XOR.
|
|
||||||
if msbSet {
|
|
||||||
double.low ^= 0xe100000000000000
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// hctrReductionTable is stored irreducible polynomial's double & add precomputed results.
|
|
||||||
// 0000 - 0
|
|
||||||
// 0001 - irreducible polynomial >> 3
|
|
||||||
// 0010 - irreducible polynomial >> 2
|
|
||||||
// 0011 - (irreducible polynomial >> 3 xor irreducible polynomial >> 2)
|
|
||||||
// ...
|
|
||||||
// 1000 - just the irreducible polynomial
|
|
||||||
var hctrReductionTable = []uint16{
|
|
||||||
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
|
|
||||||
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// hctr represents a Variable-Input-Length enciphering mode with a specific block cipher,
|
// hctr represents a Variable-Input-Length enciphering mode with a specific block cipher,
|
||||||
// and specific tweak and a hash key. See
|
// and specific tweak and a hash key. See
|
||||||
// https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288
|
// https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288
|
||||||
@ -109,7 +49,7 @@ type hctr struct {
|
|||||||
tweak [blockSize]byte
|
tweak [blockSize]byte
|
||||||
// productTable contains the first sixteen powers of the hash key.
|
// productTable contains the first sixteen powers of the hash key.
|
||||||
// However, they are in bit reversed order.
|
// However, they are in bit reversed order.
|
||||||
productTable [16]hctrFieldElement
|
productTable [16]ghashFieldElement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hctr) BlockSize() int {
|
func (h *hctr) BlockSize() int {
|
||||||
@ -130,56 +70,25 @@ func NewHCTR(cipher cipher.Block, tweak, hkey []byte) (LengthPreservingMode, err
|
|||||||
// therefore the bits will be in the reverse order. So normally one
|
// therefore the bits will be in the reverse order. So normally one
|
||||||
// would expect, say, 4*key to be in index 4 of the table but due to
|
// would expect, say, 4*key to be in index 4 of the table but due to
|
||||||
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
||||||
x := hctrFieldElement{
|
x := ghashFieldElement{
|
||||||
byteorder.BEUint64(hkey[:8]),
|
byteorder.BEUint64(hkey[:8]),
|
||||||
byteorder.BEUint64(hkey[8:blockSize]),
|
byteorder.BEUint64(hkey[8:blockSize]),
|
||||||
}
|
}
|
||||||
c.productTable[reverseBits(1)] = x
|
c.productTable[reverseBits(1)] = x
|
||||||
|
|
||||||
for i := 2; i < 16; i += 2 {
|
for i := 2; i < 16; i += 2 {
|
||||||
c.productTable[reverseBits(i)] = hctrDouble(&c.productTable[reverseBits(i/2)])
|
c.productTable[reverseBits(i)] = ghashDouble(&c.productTable[reverseBits(i/2)])
|
||||||
c.productTable[reverseBits(i+1)] = hctrAdd(&c.productTable[reverseBits(i)], &x)
|
c.productTable[reverseBits(i+1)] = ghashAdd(&c.productTable[reverseBits(i)], &x)
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mul sets y to y*H, where H is the GCM key, fixed during NewHCTR.
|
// mul sets y to y*H, where H is the GCM key, fixed during NewHCTR.
|
||||||
func (h *hctr) mul(y *hctrFieldElement) {
|
func (h *hctr) mul(y *ghashFieldElement) {
|
||||||
var z hctrFieldElement
|
ghashMul(&h.productTable, y)
|
||||||
|
|
||||||
// Eliminate bounds checks in the loop.
|
|
||||||
_ = hctrReductionTable[0xf]
|
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
word := y.high
|
|
||||||
if i == 1 {
|
|
||||||
word = y.low
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplication works by multiplying z by 16 and adding in
|
|
||||||
// one of the precomputed multiples of hash key.
|
|
||||||
for j := 0; j < 64; j += 4 {
|
|
||||||
msw := z.high & 0xf
|
|
||||||
z.high >>= 4
|
|
||||||
z.high |= z.low << 60
|
|
||||||
z.low >>= 4
|
|
||||||
z.low ^= uint64(hctrReductionTable[msw]) << 48
|
|
||||||
|
|
||||||
// the values in |table| are ordered for
|
|
||||||
// little-endian bit positions. See the comment
|
|
||||||
// in NewHCTR.
|
|
||||||
t := &h.productTable[word&0xf]
|
|
||||||
|
|
||||||
z.low ^= t.low
|
|
||||||
z.high ^= t.high
|
|
||||||
word >>= 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*y = z
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hctr) updateBlock(block []byte, y *hctrFieldElement) {
|
func (h *hctr) updateBlock(block []byte, y *ghashFieldElement) {
|
||||||
y.low ^= byteorder.BEUint64(block)
|
y.low ^= byteorder.BEUint64(block)
|
||||||
y.high ^= byteorder.BEUint64(block[8:])
|
y.high ^= byteorder.BEUint64(block[8:])
|
||||||
h.mul(y)
|
h.mul(y)
|
||||||
@ -188,7 +97,7 @@ func (h *hctr) updateBlock(block []byte, y *hctrFieldElement) {
|
|||||||
// Universal Hash Function.
|
// Universal Hash Function.
|
||||||
// Chapter 3.3 in https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288.
|
// Chapter 3.3 in https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288.
|
||||||
func (h *hctr) uhash(m []byte, out *[blockSize]byte) {
|
func (h *hctr) uhash(m []byte, out *[blockSize]byte) {
|
||||||
var y hctrFieldElement
|
var y ghashFieldElement
|
||||||
msg := m
|
msg := m
|
||||||
// update blocks
|
// update blocks
|
||||||
for len(msg) >= blockSize {
|
for len(msg) >= blockSize {
|
||||||
|
185
cipher/mur.go
Normal file
185
cipher/mur.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/internal/alias"
|
||||||
|
"github.com/emmansun/gmsm/internal/byteorder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StreamCipherCreator func(key, iv []byte) (cipher.Stream, error)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxIVSize = 32
|
||||||
|
maxTagSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type mur struct {
|
||||||
|
streamCipherCreator StreamCipherCreator
|
||||||
|
|
||||||
|
tagSize int
|
||||||
|
// productTable contains the first sixteen powers of the hash key.
|
||||||
|
// However, they are in bit reversed order.
|
||||||
|
productTable [16]ghashFieldElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMUR creates a new MUR (misuse-resistant AEAD mode) instance with a default tag size of 16 bytes.
|
||||||
|
// It takes a StreamCipherCreator function for generating the underlying stream cipher and an ghash key.
|
||||||
|
func NewMUR(streamCipherCreator StreamCipherCreator, hkey []byte) (*mur, error) {
|
||||||
|
return NewMURWithTagSize(streamCipherCreator, hkey, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMURWithTagSize creates a new MUR (misuse-resistant AEAD mode) instance with the specified tag size.
|
||||||
|
func NewMURWithTagSize(streamCipherCreator StreamCipherCreator, hkey []byte, tagSize int) (*mur, error) {
|
||||||
|
if len(hkey) != ghashBlockSize {
|
||||||
|
return nil, errors.New("cipher: invalid hash key length")
|
||||||
|
}
|
||||||
|
if tagSize < 8 || tagSize > 16 {
|
||||||
|
return nil, errors.New("cipher: invalid tag size")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &mur{}
|
||||||
|
c.streamCipherCreator = streamCipherCreator
|
||||||
|
c.tagSize = tagSize
|
||||||
|
// We precompute 16 multiples of |key|. However, when we do lookups
|
||||||
|
// into this table we'll be using bits from a field element and
|
||||||
|
// therefore the bits will be in the reverse order. So normally one
|
||||||
|
// would expect, say, 4*key to be in index 4 of the table but due to
|
||||||
|
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
||||||
|
x := ghashFieldElement{
|
||||||
|
byteorder.BEUint64(hkey[:8]),
|
||||||
|
byteorder.BEUint64(hkey[8:ghashBlockSize]),
|
||||||
|
}
|
||||||
|
c.productTable[reverseBits(1)] = x
|
||||||
|
|
||||||
|
for i := 2; i < 16; i += 2 {
|
||||||
|
c.productTable[reverseBits(i)] = ghashDouble(&c.productTable[reverseBits(i/2)])
|
||||||
|
c.productTable[reverseBits(i+1)] = ghashAdd(&c.productTable[reverseBits(i)], &x)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overhead returns the maximum difference between the lengths of a
|
||||||
|
// plaintext and its ciphertext.
|
||||||
|
func (g *mur) Overhead() int {
|
||||||
|
return g.tagSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal encrypts and authenticates plaintext, authenticates the
|
||||||
|
// additional data and appends the result to dst, returning the updated
|
||||||
|
// slice. The nonce must be NonceSize() bytes long and unique for all
|
||||||
|
// time, for a given key.
|
||||||
|
//
|
||||||
|
// To reuse plaintext's storage for the encrypted output, use plaintext[:0]
|
||||||
|
// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
|
||||||
|
// dst and additionalData may not overlap.
|
||||||
|
func (g *mur) Seal(iv, key1, key2, dst, plaintext, additionalData []byte) ([]byte, error) {
|
||||||
|
ret, out := alias.SliceForAppend(dst, len(plaintext)+g.tagSize)
|
||||||
|
if alias.InexactOverlap(out, plaintext) {
|
||||||
|
panic("cipher: invalid buffer overlap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpIV [maxIVSize]byte
|
||||||
|
tag [maxTagSize]byte
|
||||||
|
ivLen = len(iv)
|
||||||
|
)
|
||||||
|
|
||||||
|
if ivLen > maxIVSize {
|
||||||
|
panic("cipher: iv too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(tmpIV[:], iv)
|
||||||
|
g.murAuth(tmpIV[:], plaintext, additionalData)
|
||||||
|
subtle.XORBytes(tmpIV[:], tmpIV[:], iv)
|
||||||
|
tagStream, err := g.streamCipherCreator(key2, tmpIV[:ivLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tagStream.XORKeyStream(tag[:g.tagSize], tag[:g.tagSize])
|
||||||
|
|
||||||
|
clear(tmpIV[:])
|
||||||
|
subtle.XORBytes(tmpIV[:], iv, tag[:])
|
||||||
|
dataStream, err := g.streamCipherCreator(key1, tmpIV[:ivLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataStream.XORKeyStream(out, plaintext)
|
||||||
|
copy(out[len(plaintext):], tag[:g.tagSize])
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open decrypts and authenticates ciphertext, authenticates the
|
||||||
|
// additional data and, if successful, appends the resulting plaintext
|
||||||
|
// to dst, returning the updated slice. The nonce must be NonceSize()
|
||||||
|
// bytes long and both it and the additional data must match the
|
||||||
|
// value passed to Seal.
|
||||||
|
//
|
||||||
|
// To reuse ciphertext's storage for the decrypted output, use ciphertext[:0]
|
||||||
|
// as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.
|
||||||
|
// dst and additionalData may not overlap.
|
||||||
|
//
|
||||||
|
// Even if the function fails, the contents of dst, up to its capacity,
|
||||||
|
// may be overwritten.
|
||||||
|
func (g *mur) Open(iv, key1, key2, dst, ciphertext, additionalData []byte) ([]byte, error) {
|
||||||
|
if len(ciphertext) < g.tagSize {
|
||||||
|
return nil, errOpen
|
||||||
|
}
|
||||||
|
ret, out := alias.SliceForAppend(dst, len(ciphertext)-g.tagSize)
|
||||||
|
if alias.InexactOverlap(out, ciphertext) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and input")
|
||||||
|
}
|
||||||
|
if alias.AnyOverlap(out, additionalData) {
|
||||||
|
panic("cipher: invalid buffer overlap of output and additional data")
|
||||||
|
}
|
||||||
|
tag := ciphertext[len(ciphertext)-g.tagSize:]
|
||||||
|
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpIV [maxIVSize]byte
|
||||||
|
calTag [maxTagSize]byte
|
||||||
|
ivLen = len(iv)
|
||||||
|
)
|
||||||
|
if ivLen > maxIVSize {
|
||||||
|
panic("cipher: iv too large")
|
||||||
|
}
|
||||||
|
copy(tmpIV[:], tag)
|
||||||
|
subtle.XORBytes(tmpIV[:], iv, tmpIV[:])
|
||||||
|
dataStream, err := g.streamCipherCreator(key1, tmpIV[:ivLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataStream.XORKeyStream(out, ciphertext)
|
||||||
|
|
||||||
|
clear(tmpIV[:])
|
||||||
|
g.murAuth(tmpIV[:], out, additionalData)
|
||||||
|
subtle.XORBytes(tmpIV[:], tmpIV[:], iv)
|
||||||
|
tagStream, err := g.streamCipherCreator(key2, tmpIV[:ivLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tagStream.XORKeyStream(calTag[:g.tagSize], calTag[:g.tagSize])
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(tag, calTag[:g.tagSize]) != 1 {
|
||||||
|
clear(out)
|
||||||
|
return nil, errOpen
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mur) murAuth(out []byte, plaintext, additionalData []byte) {
|
||||||
|
var tag [ghashBlockSize]byte
|
||||||
|
tagField := ghashFieldElement{}
|
||||||
|
ghashUpdate(&g.productTable, &tagField, additionalData)
|
||||||
|
ghashUpdate(&g.productTable, &tagField, plaintext)
|
||||||
|
lenBlock := make([]byte, 16)
|
||||||
|
byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8)
|
||||||
|
byteorder.BEPutUint64(lenBlock[8:], uint64(len(plaintext))*8)
|
||||||
|
ghashUpdate(&g.productTable, &tagField, lenBlock)
|
||||||
|
byteorder.BEPutUint64(tag[:], tagField.low)
|
||||||
|
byteorder.BEPutUint64(tag[8:], tagField.high)
|
||||||
|
copy(out, tag[:])
|
||||||
|
}
|
118
cipher/zuc_gxm_test.go
Normal file
118
cipher/zuc_gxm_test.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package cipher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/cipher"
|
||||||
|
"github.com/emmansun/gmsm/zuc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GM/T 0001.4 - 2024 Appendix C.2
|
||||||
|
var gxmTestCases = []struct {
|
||||||
|
iv string
|
||||||
|
h string
|
||||||
|
k string
|
||||||
|
a string
|
||||||
|
p string
|
||||||
|
result string
|
||||||
|
tagSize int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
iv: "b3a6db3c870c3e99245e0d1c06b747de",
|
||||||
|
h: "6db45e4f9572f4e6fe0d91acda6801d5",
|
||||||
|
k: "edbe06afed8075576aad04afdec91d32",
|
||||||
|
a: "9de18b1fdab0ca9902b9729d492c807ec599d5",
|
||||||
|
p: "",
|
||||||
|
result: "2a14afaeb6e5ecc784fad24ddeb457d2",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "2923be84e16cd6ae529049f1f1bbe9eb",
|
||||||
|
h: "27bede74018082da87d4e5b69f18bf66",
|
||||||
|
k: "32070e0f39b7b692b4673edc3184a48e",
|
||||||
|
a: "",
|
||||||
|
p: "",
|
||||||
|
result: "5d8a045ac89a681a4bc910380bbadccf",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "2d2086832cc2fe3fd18cb51d6c5e99a5",
|
||||||
|
h: "9d6cb51623fd847f2e45d7f52f900db8",
|
||||||
|
k: "56131c03e457f6226b5477633b873984",
|
||||||
|
a: "",
|
||||||
|
p: "ffffffffffffffffffffffffffffff",
|
||||||
|
result: "b78e2f30cf70252d58767997f1b086efb30febbfe0c88a1e77b1dde9d45525",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "bb8b76cfe5f0d9335029008b2a3b2b21",
|
||||||
|
h: "ee767d503bb3d5d1b585f57a0418c673",
|
||||||
|
k: "e4b5c1f8578034ce6424f58c675597ac",
|
||||||
|
a: "fcdd4cb97995da30efd957194eac4d2a8610470f99c88657f462f68dff7561a5",
|
||||||
|
p: "5fee5517627f17b22a96caf97b77ec7f667cc47d13c34923be2441300066a6c150b24d66c947ca7b2e708eb62bb352",
|
||||||
|
result: "b56da5c99238b04a45e3d9d96f12f3dc052e428fa5a5817292ee23dbdad9782cf66f55c846e55dc68f47eaf8378e7051c7aedd9e1c7d74c38059f5e7e3a742",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "3615df810cc677f15080faa1dd44aad3",
|
||||||
|
h: "fdfaddc476785c25906fe42ba63a93b7",
|
||||||
|
k: "f405d652b6362e70f8362bd383b7298b",
|
||||||
|
a: "5fee5517627f17b22a96caf97b77ec7f667cc47d13c34923be2441300066a6c150b24d66c947ca7b2e708eb62bb352fc",
|
||||||
|
p: "dd4cb97995da30efd957194eac4d2a8610470f99c88657f462f68dff7561a5f3",
|
||||||
|
result: "1134ffc119ad163e914989474be6c072fd5867f3989d8b15899ebd10a4a248c98829aaa4f9891822",
|
||||||
|
tagSize: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGXMSeal(t *testing.T) {
|
||||||
|
for i, tc := range gxmTestCases {
|
||||||
|
key, _ := hex.DecodeString(tc.k)
|
||||||
|
iv, _ := hex.DecodeString(tc.iv)
|
||||||
|
h, _ := hex.DecodeString(tc.h)
|
||||||
|
a, _ := hex.DecodeString(tc.a)
|
||||||
|
p, _ := hex.DecodeString(tc.p)
|
||||||
|
expected, _ := hex.DecodeString(tc.result)
|
||||||
|
|
||||||
|
eea, err := zuc.NewCipher(key, iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: NewCipher error: %s", i, err)
|
||||||
|
}
|
||||||
|
c, err := cipher.NewGXMWithTagSize(eea, h, tc.tagSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: NewGXM error: %s", i, err)
|
||||||
|
}
|
||||||
|
out := c.Seal(nil, p, a)
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Errorf("case %d: incorrect ciphertext\n got: %x\nwant: %x", i, out, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGXMOpen(t *testing.T) {
|
||||||
|
for i, tc := range gxmTestCases {
|
||||||
|
key, _ := hex.DecodeString(tc.k)
|
||||||
|
iv, _ := hex.DecodeString(tc.iv)
|
||||||
|
h, _ := hex.DecodeString(tc.h)
|
||||||
|
a, _ := hex.DecodeString(tc.a)
|
||||||
|
p, _ := hex.DecodeString(tc.p)
|
||||||
|
expected, _ := hex.DecodeString(tc.result)
|
||||||
|
|
||||||
|
eea, err := zuc.NewCipher(key, iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: NewCipher error: %s", i, err)
|
||||||
|
}
|
||||||
|
c, err := cipher.NewGXMWithTagSize(eea, h, tc.tagSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: NewGXM error: %s", i, err)
|
||||||
|
}
|
||||||
|
out, err := c.Open(nil, expected, a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: Open error: %s", i, err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, p) {
|
||||||
|
t.Errorf("case %d: incorrect plaintext\n got: %x\nwant: %x", i, out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
cipher/zuc_mur_test.go
Normal file
134
cipher/zuc_mur_test.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package cipher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_cipher "crypto/cipher"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/cipher"
|
||||||
|
"github.com/emmansun/gmsm/zuc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var murTestCases = []struct {
|
||||||
|
iv string
|
||||||
|
h string
|
||||||
|
k1 string
|
||||||
|
k2 string
|
||||||
|
a string
|
||||||
|
p string
|
||||||
|
result string
|
||||||
|
tagSize int
|
||||||
|
}{
|
||||||
|
// GM/T 0001.4 - 2024 Appendix C.3
|
||||||
|
{
|
||||||
|
iv: "bb8b76cfe5f0d9335029008b2a3b2b21",
|
||||||
|
h: "ee767d503bb3d5d1b585f57a0418c673",
|
||||||
|
k1: "e4b5c1f8578034ce6424f58c675597ac",
|
||||||
|
k2: "608053f6af9efda562d95dc013bea6b5",
|
||||||
|
a: "fcdd4cb97995da30efd957194eac4d2a8610470f99c88657f462f68dff7561a5",
|
||||||
|
p: "5fee5517627f17b22a96caf97b77ec7f667cc47d13c34923be2441300066a6c150b24d66c947ca7b2e708eb62bb352",
|
||||||
|
result: "cf5594bd30c0da0fb41fa6054e534d0494c9d6c4f132fc85771a473458b09583b825c662bfd82278178a845e281e5415c5d1a78a42c4dcd67db05fa1a640a0",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "2923be84e16cd6ae529049f1f1bbe9eb",
|
||||||
|
h: "27bede74018082da87d4e5b69f18bf66",
|
||||||
|
k1: "32070e0f39b7b692b4673edc3184a48e",
|
||||||
|
k2: "27636f4414510d62cc15cfe194ec4f6d",
|
||||||
|
a: "",
|
||||||
|
p: "",
|
||||||
|
result: "c0016e0772c9983d0fd9fd8c1b012845",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "2d2086832cc2fe3fd18cb51d6c5e99a5",
|
||||||
|
h: "9d6cb51623fd847f2e45d7f52f900db8",
|
||||||
|
k1: "56131c03e457f6226b5477633b873984",
|
||||||
|
k2: "a88981534db331a386de3e52fb46029b",
|
||||||
|
a: "",
|
||||||
|
p: "ffffffffffffffffffffffffffffff",
|
||||||
|
result: "234c2d51eaa582da9be3cc3828aa670a7afb7d817efa0777826f1e33a53cf3",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "b3a6db3c870c3e99245e0d1c06b747de",
|
||||||
|
h: "6db45e4f9572f4e6fe0d91acda6801d5",
|
||||||
|
k1: "edbe06afed8075576aad04afdec91d32",
|
||||||
|
k2: "61d4fca6b2c2bb48b4b1172531333620",
|
||||||
|
a: "9de18b1fdab0ca9902b9729d492c807ec599d5",
|
||||||
|
p: "",
|
||||||
|
result: "8213c29606d02bba10f13ffad1d26a42",
|
||||||
|
tagSize: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iv: "b3a6db3c870c3e99245e0d1c06b747de",
|
||||||
|
h: "6db45e4f9572f4e6fe0d91acda6801d5",
|
||||||
|
k1: "edbe06afed8075576aad04afdec91d32",
|
||||||
|
k2: "61d4fca6b2c2bb48b4b1172531333620",
|
||||||
|
a: "9de18b1fdab0ca9902b9729d492c807ec599d5e980b2eac9cc53bf67d6bf14d67e2ddc8e6683ef574961ff698f61cdd1",
|
||||||
|
p: "b3124dc843bb8ba61f035a7d0938251f5dd4cbfc96f5453b130d890a1cdbae32",
|
||||||
|
result: "dabbbe23d8f0ea42e31a9bdd9706a4275d8aacd2cf27c4a4c0d0ba6fb8f31da7a276827b74509357",
|
||||||
|
tagSize: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMurSeal(t *testing.T) {
|
||||||
|
zucCipherCreator := func(key, iv []byte) (_cipher.Stream, error) {
|
||||||
|
return zuc.NewCipher(key, iv)
|
||||||
|
}
|
||||||
|
for i, tc := range murTestCases {
|
||||||
|
iv, _ := hex.DecodeString(tc.iv)
|
||||||
|
h, _ := hex.DecodeString(tc.h)
|
||||||
|
k1, _ := hex.DecodeString(tc.k1)
|
||||||
|
k2, _ := hex.DecodeString(tc.k2)
|
||||||
|
a, _ := hex.DecodeString(tc.a)
|
||||||
|
p, _ := hex.DecodeString(tc.p)
|
||||||
|
result, _ := hex.DecodeString(tc.result)
|
||||||
|
|
||||||
|
g, err := cipher.NewMURWithTagSize(zucCipherCreator, h, tc.tagSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: NewMURWithTagSize error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, err := g.Seal(iv, k1, k2, nil, p, a)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: Seal error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c, result) {
|
||||||
|
t.Errorf("case %d: Seal mismatch\ngot: %x\nwant: %x", i, c, result)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMurOpen(t *testing.T) {
|
||||||
|
zucCipherCreator := func(key, iv []byte) (_cipher.Stream, error) {
|
||||||
|
return zuc.NewCipher(key, iv)
|
||||||
|
}
|
||||||
|
for i, tc := range murTestCases {
|
||||||
|
iv, _ := hex.DecodeString(tc.iv)
|
||||||
|
h, _ := hex.DecodeString(tc.h)
|
||||||
|
k1, _ := hex.DecodeString(tc.k1)
|
||||||
|
k2, _ := hex.DecodeString(tc.k2)
|
||||||
|
a, _ := hex.DecodeString(tc.a)
|
||||||
|
p, _ := hex.DecodeString(tc.p)
|
||||||
|
result, _ := hex.DecodeString(tc.result)
|
||||||
|
|
||||||
|
g, err := cipher.NewMURWithTagSize(zucCipherCreator, h, tc.tagSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: NewMURWithTagSize error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out, err := g.Open(iv, k1, k2, nil, result, a)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: Open error: %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, p) {
|
||||||
|
t.Errorf("case %d: Open mismatch\ngot: %x\nwant: %x", i, out, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user