diff --git a/zuc/core.go b/zuc/core.go index 95d1fb9..0640bca 100644 --- a/zuc/core.go +++ b/zuc/core.go @@ -1,4 +1,4 @@ -// Package zuc handle shangmi zuc stream cipher +// Package zuc handle shangmi zuc stream cipher, experimental/poc implementation. package zuc import ( @@ -57,6 +57,21 @@ var zuc256_d0 = [16]byte{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x52, 0x10, 0x30, } +var zuc256_d = [][16]byte{ + { + 0x22, 0x2F, 0x25, 0x2A, 0x6D, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x52, 0x10, 0x30, + }, + { + 0x23, 0x2F, 0x24, 0x2A, 0x6D, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x52, 0x10, 0x30, + }, + { + 0x23, 0x2F, 0x25, 0x2A, 0x6D, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x52, 0x10, 0x30, + }, +} + type zucState32 struct { lfsr [16]uint32 // linear feedback shift register r1 uint32 diff --git a/zuc/eia.go b/zuc/eia.go index 7f4eb02..d0bd7b3 100644 --- a/zuc/eia.go +++ b/zuc/eia.go @@ -23,7 +23,6 @@ type ZUC128Mac struct { } // NewHash create hash for zuc-128 eia, with arguments key and iv. -// Hash byte (8 bits) level. func NewHash(key, iv []byte) (*ZUC128Mac, error) { k := len(key) ivLen := len(iv) diff --git a/zuc/eia256.go b/zuc/eia256.go new file mode 100644 index 0000000..bef0ae5 --- /dev/null +++ b/zuc/eia256.go @@ -0,0 +1,191 @@ +package zuc + +import ( + "encoding/binary" + "fmt" +) + +type ZUC256Mac struct { + zucState32 + initState zucState32 + tagSize int + k0 []uint32 + t []uint32 + x [chunk]byte + nx int + len uint64 +} + +// NewHash create hash for zuc-128 eia, with arguments key, iv and tagSize. +// The larger the tag size, the worse the performance. +func NewHash256(key, iv []byte, tagSize int) (*ZUC256Mac, error) { + k := len(key) + ivLen := len(iv) + mac := &ZUC256Mac{} + var d []byte + switch tagSize { + default: + return nil, fmt.Errorf("zuc/eia: invalid tag size %d, support 4/8/16 in bytes", tagSize) + case 4: + d = zuc256_d[0][:] + case 8: + d = zuc256_d[1][:] + case 16: + d = zuc256_d[2][:] + } + mac.tagSize = tagSize + mac.t = make([]uint32, mac.tagSize/4) + mac.k0 = make([]uint32, mac.tagSize/4) + switch k { + default: + return nil, fmt.Errorf("zuc/eia: invalid key size %d, expect 32 in bytes", k) + case 32: // ZUC-256 + if ivLen != 23 { + return nil, fmt.Errorf("zuc/eia: invalid iv size %d, expect 23 in bytes", ivLen) + } + mac.loadKeyIV32(key, iv, d) + } + // initialization + for i := 0; i < 32; i++ { + x := mac.bitReconstruction() + w := mac.f32(x[0], x[1], x[2]) + mac.enterInitMode(w >> 1) + } + + // work state + x := mac.bitReconstruction() + mac.f32(x[0], x[1], x[2]) + mac.enterWorkMode() + + mac.initState.r1 = mac.r1 + mac.initState.r2 = mac.r2 + + copy(mac.initState.lfsr[:], mac.lfsr[:]) + mac.Reset() + return mac, nil +} + +func (m *ZUC256Mac) Size() int { + return m.tagSize +} + +func (m *ZUC256Mac) BlockSize() int { + return chunk +} + +func (m *ZUC256Mac) Reset() { + m.nx = 0 + m.len = 0 + m.r1 = m.initState.r1 + m.r2 = m.initState.r2 + copy(m.lfsr[:], m.initState.lfsr[:]) + m.genKeywords(m.t) + m.genKeywords(m.k0) +} + +func (m *ZUC256Mac) block(p []byte) { + for len(p) >= chunk { + w := binary.BigEndian.Uint32(p) + k1 := m.genKeyword() + + for i := 0; i < 32; i++ { + if w&0x80000000 == 0x80000000 { + for j := 0; j < m.tagSize/4; j++ { + m.t[j] ^= m.k0[j] + } + } + w <<= 1 + var j int + for j = 0; j < m.tagSize/4-1; j++ { + m.k0[j] = (m.k0[j] << 1) | (m.k0[j+1] >> 31) + } + m.k0[j] = (m.k0[j] << 1) | (k1 >> 31) + k1 <<= 1 + } + + p = p[chunk:] + } +} + +func (m *ZUC256Mac) Write(p []byte) (nn int, err error) { + nn = len(p) + m.len += uint64(nn) + if m.nx > 0 { + n := copy(m.x[m.nx:], p) + m.nx += n + if m.nx == chunk { + m.block(m.x[:]) + m.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + m.block(p[:n]) + p = p[n:] + } + if len(p) > 0 { + m.nx = copy(m.x[:], p) + } + return +} + +func (m *ZUC256Mac) checkSum(additionalBits int, b byte) []byte { + if m.nx >= 4 { + panic("m.nx >= 4") + } + if m.nx > 0 || additionalBits > 0 { + m.x[m.nx] = b + w := binary.BigEndian.Uint32(m.x[:]) + k1 := m.genKeyword() + + for i := 0; i < 8*m.nx+additionalBits; i++ { + if w&0x80000000 == 0x80000000 { + for j := 0; j < m.tagSize/4; j++ { + m.t[j] ^= m.k0[j] + } + } + w <<= 1 + var j int + for j = 0; j < m.tagSize/4-1; j++ { + m.k0[j] = (m.k0[j] << 1) | (m.k0[j+1] >> 31) + } + m.k0[j] = (m.k0[j] << 1) | (k1 >> 31) + k1 <<= 1 + } + } + + digest := make([]byte, m.tagSize) + for j := 0; j < m.tagSize/4; j++ { + m.t[j] ^= m.k0[j] + binary.BigEndian.PutUint32(digest[j*4:], m.t[j]) + } + + return digest +} + +// Finish this function hash nbits data in p and return mac value +// In general, we will use byte level function, this is just for test/verify. +func (m *ZUC256Mac) Finish(p []byte, nbits int) []byte { + if len(p) < (nbits+7)/8 { + panic("invalid p length") + } + nbytes := nbits / 8 + nRemainBits := nbits - nbytes*8 + if nbytes > 0 { + m.Write(p[:nbytes]) + } + var b byte + if nRemainBits > 0 { + b = p[nbytes] + } + digest := m.checkSum(nRemainBits, b) + return digest[:] +} + +func (m *ZUC256Mac) Sum(in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + d0 := *m + hash := d0.checkSum(0, 0) + return append(in, hash[:]...) +} diff --git a/zuc/eia256_test.go b/zuc/eia256_test.go new file mode 100644 index 0000000..2ef982c --- /dev/null +++ b/zuc/eia256_test.go @@ -0,0 +1,253 @@ +package zuc + +import ( + "encoding/hex" + "testing" +) + +var zucEIA256Tests = []struct { + key []byte + iv []byte + msg []byte + nMsgs int + mac32 string + mac64 string + mac128 string +}{ + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + 1, + "9b972a74", + "673e54990034d38c", + "d85e54bbcb9600967084c952a1654b26", + }, + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + []byte{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + }, + 10, + "8754f5cf", + "130dc225e72240cc", + "df1e8307b31cc62beca1ac6f8190c22f", + }, + { + []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + 1, + "1f3079b4", + "8c71394d39957725", + "a35bb274b567c48b28319f111af34fbd", + }, + { + []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + []byte{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + }, + 10, + "5c7c8b88", + "ea1dee544bb6223b", + "3a83b554be408ca5494124ed9d473205", + }, +} + +func Test_Finish256_32(t *testing.T) { + for i, test := range zucEIA256Tests { + h, err := NewHash256(test.key, test.iv, 4) + if err != nil { + t.Error(err) + } + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest := h.Sum(nil) + if hex.EncodeToString(digest) != test.mac32 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac32, hex.EncodeToString(digest)) + } + h.Reset() + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest = h.Sum(nil) + if hex.EncodeToString(digest) != test.mac32 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac32, hex.EncodeToString(digest)) + } + } +} + +func Test_Finish256_64(t *testing.T) { + for i, test := range zucEIA256Tests { + h, err := NewHash256(test.key, test.iv, 8) + if err != nil { + t.Error(err) + } + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest := h.Sum(nil) + if hex.EncodeToString(digest) != test.mac64 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac64, hex.EncodeToString(digest)) + } + h.Reset() + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest = h.Sum(nil) + if hex.EncodeToString(digest) != test.mac64 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac64, hex.EncodeToString(digest)) + } + } +} + +func Test_Finish256_128(t *testing.T) { + for i, test := range zucEIA256Tests { + h, err := NewHash256(test.key, test.iv, 16) + if err != nil { + t.Error(err) + } + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest := h.Sum(nil) + if hex.EncodeToString(digest) != test.mac128 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac128, hex.EncodeToString(digest)) + } + h.Reset() + for j := 0; j < test.nMsgs; j++ { + _, err = h.Write(test.msg) + if err != nil { + t.Error(err) + } + } + digest = h.Sum(nil) + if hex.EncodeToString(digest) != test.mac128 { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac128, hex.EncodeToString(digest)) + } + } +} + +func benchmark256Size(b *testing.B, size, tagSize int) { + var key [32]byte + var iv [23]byte + var buf = make([]byte, 8192) + bench, _ := NewHash256(key[:], iv[:], tagSize) + b.SetBytes(int64(size)) + sum := make([]byte, bench.Size()) + for i := 0; i < b.N; i++ { + bench.Reset() + bench.Write(buf[:size]) + bench.Sum(sum[:0]) + } +} + +func BenchmarkHash8Bytes_Tag32(b *testing.B) { + benchmark256Size(b, 8, 4) +} + +func BenchmarkHash8Bytes_Tag64(b *testing.B) { + benchmark256Size(b, 8, 8) +} + +func BenchmarkHash8Bytes_Tag128(b *testing.B) { + benchmark256Size(b, 8, 16) +} + +func BenchmarkHash1K_Tag32(b *testing.B) { + benchmark256Size(b, 1024, 4) +} + +func BenchmarkHash1K_Tag64(b *testing.B) { + benchmark256Size(b, 1024, 8) +} + +func BenchmarkHash1K_Tag128(b *testing.B) { + benchmark256Size(b, 1024, 16) +} + +func BenchmarkHash8K_Tag32(b *testing.B) { + benchmark256Size(b, 8192, 4) +} + +func BenchmarkHash8K_Tag64(b *testing.B) { + benchmark256Size(b, 8192, 8) +} + +func BenchmarkHash8K_Tag128(b *testing.B) { + benchmark256Size(b, 8192, 16) +}