diff --git a/zuc/asm_amd64.s b/internal/zuc/asm_amd64.s similarity index 100% rename from zuc/asm_amd64.s rename to internal/zuc/asm_amd64.s diff --git a/zuc/asm_arm64.s b/internal/zuc/asm_arm64.s similarity index 100% rename from zuc/asm_arm64.s rename to internal/zuc/asm_arm64.s diff --git a/zuc/asm_ppc64x.s b/internal/zuc/asm_ppc64x.s similarity index 100% rename from zuc/asm_ppc64x.s rename to internal/zuc/asm_ppc64x.s diff --git a/zuc/core.go b/internal/zuc/core.go similarity index 96% rename from zuc/core.go rename to internal/zuc/core.go index 048e890..e3799aa 100644 --- a/zuc/core.go +++ b/internal/zuc/core.go @@ -2,8 +2,8 @@ package zuc import ( - "fmt" "math/bits" + "strconv" "github.com/emmansun/gmsm/internal/byteorder" ) @@ -195,21 +195,33 @@ func (s *zucState32) loadKeyIV32(key, iv, d []byte) { s.lfsr[14] = makeFieldValue4(uint32(key[14]), uint32(d[14]|(key[31]>>4)), uint32(iv[16]), uint32(iv[9])) } +type KeySizeError int + +func (k KeySizeError) Error() string { + return "zuc: invalid key size " + strconv.Itoa(int(k)) +} + +type IVSizeError int + +func (k IVSizeError) Error() string { + return "zuc: invalid IV size " + strconv.Itoa(int(k)) +} + func newZUCState(key, iv []byte) (*zucState32, error) { k := len(key) ivLen := len(iv) state := &zucState32{} switch k { default: - return nil, fmt.Errorf("zuc: invalid key size %d, we support 16/32 now", k) + return nil, KeySizeError(k) case 16: // ZUC-128 if ivLen != IVSize128 { - return nil, fmt.Errorf("zuc: invalid iv size %d, expect %d in bytes", ivLen, IVSize128) + return nil, IVSizeError(ivLen) } state.loadKeyIV16(key, iv) case 32: // ZUC-256 if ivLen != IVSize256 { - return nil, fmt.Errorf("zuc: invalid iv size %d, expect %d in bytes", ivLen, IVSize256) + return nil, IVSizeError(ivLen) } state.loadKeyIV32(key, iv, zuc256_d0[:]) } diff --git a/zuc/core_asm.go b/internal/zuc/core_asm.go similarity index 100% rename from zuc/core_asm.go rename to internal/zuc/core_asm.go diff --git a/zuc/core_generic.go b/internal/zuc/core_generic.go similarity index 100% rename from zuc/core_generic.go rename to internal/zuc/core_generic.go diff --git a/zuc/core_test.go b/internal/zuc/core_test.go similarity index 100% rename from zuc/core_test.go rename to internal/zuc/core_test.go diff --git a/internal/zuc/eea.go b/internal/zuc/eea.go new file mode 100644 index 0000000..b01e875 --- /dev/null +++ b/internal/zuc/eea.go @@ -0,0 +1,166 @@ +package zuc + +import ( + "github.com/emmansun/gmsm/internal/alias" + "github.com/emmansun/gmsm/internal/byteorder" + "github.com/emmansun/gmsm/internal/subtle" +) + +const ( + // number of words in a round + RoundWords = 32 + // number of bytes in a word + WordSize = 4 + WordMask = WordSize - 1 + // number of bytes in a round + RoundBytes = RoundWords * WordSize +) + +type eea struct { + zucState32 + x [WordSize]byte // remaining bytes buffer + xLen int // number of remaining bytes + initState zucState32 // initial state for reset + used uint64 // number of key bytes processed, current offset +} + +// NewCipher create a stream cipher based on key and iv aguments. +// The key must be 16 bytes long and iv must be 16 bytes long for zuc 128; +// or the key must be 32 bytes long and iv must be 23 bytes long for zuc 256; +// otherwise, an error will be returned. +func NewCipher(key, iv []byte) (*eea, error) { + s, err := newZUCState(key, iv) + if err != nil { + return nil, err + } + c := new(eea) + c.zucState32 = *s + c.initState = *s + c.used = 0 + return c, nil +} + +// NewEEACipher create a stream cipher based on key, count, bearer and direction arguments according specification. +// The key must be 16 bytes long and iv must be 16 bytes long, otherwise, an error will be returned. +// The count is the 32-bit counter value, the bearer is the 5-bit bearer identity and the direction is the 1-bit +// transmission direction flag. +func NewEEACipher(key []byte, count, bearer, direction uint32) (*eea, error) { + iv := make([]byte, 16) + byteorder.BEPutUint32(iv, count) + copy(iv[8:12], iv[:4]) + iv[4] = byte(((bearer << 1) | (direction & 1)) << 2) + iv[12] = iv[4] + return NewCipher(key, iv) +} + +func genKeyStreamRev32Generic(keyStream []byte, pState *zucState32) { + for len(keyStream) >= WordSize { + z := genKeyword(pState) + byteorder.BEPutUint32(keyStream, z) + keyStream = keyStream[WordSize:] + } +} + +func (c *eea) XORKeyStream(dst, src []byte) { + if len(dst) < len(src) { + panic("zuc: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("zuc: invalid buffer overlap") + } + used := len(src) + if c.xLen > 0 { + // handle remaining key bytes + n := subtle.XORBytes(dst, src, c.x[:c.xLen]) + c.xLen -= n + dst = dst[n:] + src = src[n:] + if c.xLen > 0 { + copy(c.x[:], c.x[n:c.xLen+n]) + c.used += uint64(used) + return + } + } + var keyBytes [RoundBytes]byte + for len(src) >= RoundBytes { + genKeyStreamRev32(keyBytes[:], &c.zucState32) + subtle.XORBytes(dst, src, keyBytes[:]) + dst = dst[RoundBytes:] + src = src[RoundBytes:] + } + if len(src) > 0 { + byteLen := (len(src) + WordMask) &^ WordMask + genKeyStreamRev32(keyBytes[:byteLen], &c.zucState32) + n := subtle.XORBytes(dst, src, keyBytes[:]) + // save remaining key bytes + c.xLen = byteLen - n + if c.xLen > 0 { + copy(c.x[:], keyBytes[n:byteLen]) + } + } + c.used += uint64(used) +} + +func (c *eea) reset() { + c.zucState32 = c.initState + c.xLen = 0 + c.used = 0 +} + +// seek sets the offset for the next XORKeyStream operation. +// +// If the offset is less than the current offset, the state will be reset to the initial state. +// If the offset is equal to the current offset, the function behaves the same as XORKeyStream. +// If the offset is greater than the current offset, the function will forward the state to the offset. +// Note: This method is not thread-safe. +func (c *eea) seek(offset uint64) { + if offset < c.used { + c.reset() + } + if offset == c.used { + return + } + gap := offset - c.used + if gap <= uint64(c.xLen) { + // offset is within the remaining key bytes + c.xLen -= int(gap) + c.used += gap + if c.xLen > 0 { + // adjust remaining key bytes + copy(c.x[:], c.x[gap:]) + } + return + } + // consumed all remaining key bytes first + if c.xLen > 0 { + c.used += uint64(c.xLen) + gap -= uint64(c.xLen) + c.xLen = 0 + } + + // forward the state to the offset + c.used += gap + stepLen := uint64(RoundBytes) + var keyStream [RoundWords]uint32 + for gap >= stepLen { + genKeyStream(keyStream[:], &c.zucState32) + gap -= stepLen + } + + if gap > 0 { + numWords := (gap + WordMask) / WordSize + genKeyStream(keyStream[:numWords], &c.zucState32) + partiallyUsed := int(gap & WordMask) + if partiallyUsed > 0 { + // save remaining key bytes (less than 4 bytes) + c.xLen = WordSize - partiallyUsed + byteorder.BEPutUint32(c.x[:], keyStream[numWords-1]) + copy(c.x[:], c.x[partiallyUsed:]) + } + } +} + +func (c *eea) XORKeyStreamAt(dst, src []byte, offset uint64) { + c.seek(offset) + c.XORKeyStream(dst, src) +} diff --git a/zuc/eea_asm.go b/internal/zuc/eea_asm.go similarity index 100% rename from zuc/eea_asm.go rename to internal/zuc/eea_asm.go diff --git a/zuc/eea_generic.go b/internal/zuc/eea_generic.go similarity index 100% rename from zuc/eea_generic.go rename to internal/zuc/eea_generic.go diff --git a/internal/zuc/eea_test.go b/internal/zuc/eea_test.go new file mode 100644 index 0000000..a192831 --- /dev/null +++ b/internal/zuc/eea_test.go @@ -0,0 +1,259 @@ +package zuc + +import ( + "bytes" + "crypto/cipher" + "encoding/hex" + "testing" + + "github.com/emmansun/gmsm/internal/cryptotest" +) + +var zucEEATests = []struct { + key string + count uint32 + bearer uint32 + direction uint32 + in string + out string +}{ + { + "173d14ba5003731d7a60049470f00a29", + 0x66035492, + 0xf, + 0, + "6cf65340735552ab0c9752fa6f9025fe0bd675d9005875b2", + "a6c85fc66afb8533aafc2518dfe784940ee1e4b030238cc8", + }, + { + "e5bd3ea0eb55ade866c6ac58bd54302a", + 0x56823, + 0x18, + 1, + "14a8ef693d678507bbe7270a7f67ff5006c3525b9807e467c4e56000ba338f5d429559036751822246c80d3b38f07f4be2d8ff5805f5132229bde93bbbdcaf382bf1ee972fbf9977bada8945847a2a6c9ad34a667554e04d1f7fa2c33241bd8f01ba220d", + "131d43e0dea1be5c5a1bfd971d852cbf712d7b4f57961fea3208afa8bca433f456ad09c7417e58bc69cf8866d1353f74865e80781d202dfb3ecff7fcbc3b190fe82a204ed0e350fc0f6f2613b2f2bca6df5a473a57a4a00d985ebad880d6f23864a07b01", + }, + { + "e13fed21b46e4e7ec31253b2bb17b3e0", + 0x2738cdaa, + 0x1a, + 0, + "8d74e20d54894e06d3cb13cb3933065e8674be62adb1c72b3a646965ab63cb7b7854dfdc27e84929f49c64b872a490b13f957b64827e71f41fbd4269a42c97f824537027f86e9f4ad82d1df451690fdd98b6d03f3a0ebe3a312d6b840ba5a1820b2a2c9709c090d245ed267cf845ae41fa975d3333ac3009fd40eba9eb5b885714b768b697138baf21380eca49f644d48689e4215760b906739f0d2b3f091133ca15d981cbe401baf72d05ace05cccb2d297f4ef6a5f58d91246cfa77215b892ab441d5278452795ccb7f5d79057a1c4f77f80d46db2033cb79bedf8e60551ce10c667f62a97abafabbcd6772018df96a282ea737ce2cb331211f60d5354ce78f9918d9c206ca042c9b62387dd709604a50af16d8d35a8906be484cf2e74a9289940364353249b27b4c9ae29eddfc7da6418791a4e7baa0660fa64511f2d685cc3a5ff70e0d2b74292e3b8a0cd6b04b1c790b8ead2703708540dea2fc09c3da770f65449c84d817a4f551055e19ab85018a0028b71a144d96791e9a3577933504eee0060340c69d274e1bf9d805dcbcc1a6faa976800b6ff2b671dc463652fa8a33ee50974c1c21be01eabb2167430269d72ee511c9dde30797c9a25d86ce74f5b961be5fdfb6807814039e7137636bd1d7fa9e09efd2007505906a5ac45dfdeed7757bbee745749c29633350bee0ea6f409df458016", + "94eaa4aa30a57137ddf09b97b25618a20a13e2f10fa5bf8161a879cc2ae797a6b4cf2d9df31debb9905ccfec97de605d21c61ab8531b7f3c9da5f03931f8a0642de48211f5f52ffea10f392a047669985da454a28f080961a6c2b62daa17f33cd60a4971f48d2d909394a55f48117ace43d708e6b77d3dc46d8bc017d4d1abb77b7428c042b06f2f99d8d07c9879d99600127a31985f1099bbd7d6c1519ede8f5eeb4a610b349ac01ea2350691756bd105c974a53eddb35d1d4100b012e522ab41f4c5f2fde76b59cb8b96d885cfe4080d1328a0d636cc0edc05800b76acca8fef672084d1f52a8bbd8e0993320992c7ffbae17c408441e0ee883fc8a8b05e22f5ff7f8d1b48c74c468c467a028f09fd7ce91109a570a2d5c4d5f4fa18c5dd3e4562afe24ef771901f59af645898acef088abae07e92d52eb2de55045bb1b7c4164ef2d7a6cac15eeb926d7ea2f08b66e1f759f3aee44614725aa3c7482b30844c143ff87b53f1e583c501257dddd096b81268daa303f17234c2333541f0bb8e190648c5807c866d7193228609adb948686f7de294a802cc38f7fe5208f5ea3196d0167b9bdd02f0d2a5221ca508f893af5c4b4bb9f4f520fd84289b3dbe7e61497a7e2a584037ea637b6981127174af57b471df4b2768fd79c1540fb3edf2ea22cb69bec0cf8d933d9c6fdd645e850591cca3d62c0c", + }, +} + +func Test_EEA(t *testing.T) { + for i, test := range zucEEATests { + key, err := hex.DecodeString(test.key) + if err != nil { + t.Error(err) + } + c, err := NewEEACipher(key, test.count, test.bearer, test.direction) + if err != nil { + t.Error(err) + } + in, err := hex.DecodeString(test.in) + if err != nil { + t.Error(err) + } + out := make([]byte, len(in)) + copy(out, in) + c.XORKeyStream(out, out) + if hex.EncodeToString(out) != test.out { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.out, hex.EncodeToString(out)) + } + } +} + +func TestEEAStream(t *testing.T) { + cryptotest.TestStream(t, func() cipher.Stream { + key, _ := hex.DecodeString(zucEEATests[0].key) + c, _ := NewEEACipher(key, zucEEATests[0].count, zucEEATests[0].bearer, zucEEATests[0].direction) + return c + }) +} + +func TestXORStreamAt(t *testing.T) { + key, err := hex.DecodeString(zucEEATests[0].key) + if err != nil { + t.Error(err) + } + c, err := NewEEACipher(key, zucEEATests[0].count, zucEEATests[0].bearer, zucEEATests[0].direction) + if err != nil { + t.Error(err) + } + src := make([]byte, 1000) + expected := make([]byte, 1000) + dst := make([]byte, 1000) + c.XORKeyStream(expected, src) + + t.Run("Reset and forward to offset", func(t *testing.T) { + for i := 0; i < 65; i++ { + c.XORKeyStreamAt(dst[i:], src[i:], uint64(i)) + if !bytes.Equal(expected[i:], dst[i:]) { + t.Errorf("At %d, expected=%x, result=%x\n", i, expected[i:], dst[i:]) + } + } + }) + + t.Run("Offset equals to the current position", func(t *testing.T) { + c.XORKeyStreamAt(dst[:16], src[:16], 0) + c.XORKeyStreamAt(dst[16:32], src[16:32], 16) + if !bytes.Equal(dst[:32], expected[:32]) { + t.Errorf("expected=%x, result=%x\n", expected[:32], dst[:32]) + } + }) + + t.Run("Jump and forward (incomplete word): gap > xLen", func(t *testing.T) { + for i := 0; i < 4; i++ { + c.XORKeyStreamAt(dst[i:16], src[i:16], uint64(i)) + c.XORKeyStreamAt(dst[32:64], src[32:64], 32) + if !bytes.Equal(dst[32:64], expected[32:64]) { + t.Errorf("expected=%x, result=%x\n", expected[32:64], dst[32:64]) + } + } + for i := 1; i < 4; i++ { + c.XORKeyStreamAt(dst[:i], src[:i], 0) + c.XORKeyStreamAt(dst[32:64], src[32:64], 32) + if !bytes.Equal(dst[32:64], expected[32:64]) { + t.Errorf("expected=%x, result=%x\n", expected[32:64], dst[32:64]) + } + } + }) + + t.Run("Jump and forward (incomplete word): gap <= xLen", func(t *testing.T) { + c.XORKeyStreamAt(dst[:1], src[:1], 0) + c.XORKeyStreamAt(dst[3:16], src[3:16], 3) + if !bytes.Equal(dst[3:16], expected[3:16]) { + t.Errorf("expected=%x, result=%x\n", expected[3:16], dst[3:16]) + } + c.XORKeyStreamAt(dst[:1], src[:1], 0) + c.XORKeyStreamAt(dst[4:16], src[4:16], 4) + if !bytes.Equal(dst[4:16], expected[4:16]) { + t.Errorf("expected=%x, result=%x\n", expected[3:16], dst[3:16]) + } + }) + + t.Run("Jump and forward (skipped keys more than 128)", func(t *testing.T) { + // test offset - used > 128 bytes case + c.XORKeyStreamAt(dst[:16], src[:16], 0) + offset := 700 + c.XORKeyStreamAt(dst[offset:], src[offset:], uint64(offset)) + if !bytes.Equal(dst[offset:], expected[offset:]) { + t.Errorf("expected=%x, result=%x\n", expected[offset:], dst[offset:]) + } + }) + + t.Run("Mixed XORKeyStreamAt with XORKeyStream", func(t *testing.T) { + // XORKeyStreamAt with XORKeyStream + c.XORKeyStreamAt(dst[:16], src[:16], 0) + c.XORKeyStream(dst[16:31], src[16:31]) + c.XORKeyStreamAt(dst[31:64], src[31:64], 31) + c.XORKeyStream(dst[64:128], src[64:128]) + if !bytes.Equal(dst[:128], expected[:128]) { + t.Errorf("expected=%x, result=%x\n", expected[:128], dst[:128]) + } + }) + + t.Run("BufferOverlap", func(t *testing.T) { + buff := make([]byte, 100) + // Make src and dst slices point to same array with inexact overlap + src := buff[:32] + dst := buff[1 : 32+1] + cryptotest.MustPanic(t, "invalid buffer overlap", func() { c.XORKeyStreamAt(dst, src, 0) }) + + // Only overlap on one byte + src = buff[:32] + dst = buff[32-1 : 2*32-1] + cryptotest.MustPanic(t, "invalid buffer overlap", func() { c.XORKeyStreamAt(dst, src, 0) }) + + // src comes after dst with one byte overlap + src = buff[32-1 : 2*32-1] + dst = buff[:32] + cryptotest.MustPanic(t, "invalid buffer overlap", func() { c.XORKeyStreamAt(dst, src, 0) }) + + // length of dst is less than src + src = buff[:32] + dst = buff[32:63] + cryptotest.MustPanic(t, "output smaller than input", func() { c.XORKeyStreamAt(dst, src, 0) }) + }) +} + +func TestIssue284(t *testing.T) { + key, err := hex.DecodeString(zucEEATests[0].key) + if err != nil { + t.Error(err) + } + c, err := NewEEACipher(key, zucEEATests[0].count, zucEEATests[0].bearer, zucEEATests[0].direction) + if err != nil { + t.Error(err) + } + src := make([]byte, RoundBytes*2) + expected := make([]byte, RoundBytes*2) + dst := make([]byte, RoundBytes*2) + c.XORKeyStream(expected, src) + + for i := RoundBytes - 3; i < RoundBytes+5; i++ { + c.XORKeyStreamAt(dst, src[:i], 0) + c.XORKeyStream(dst[i:], src[i:]) + if !bytes.Equal(expected, dst) { + t.Fatalf("failed for len %v", i) + } + } +} + +func benchmarkStream(b *testing.B, buf []byte) { + b.SetBytes(int64(len(buf))) + + var key [16]byte + var iv [16]byte + + stream, _ := NewCipher(key[:], iv[:]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + stream.XORKeyStream(buf, buf) + } +} + +const almost1K = 1024 - 5 +const almost8K = 8*1024 - 5 + +func BenchmarkEncrypt1K(b *testing.B) { + benchmarkStream(b, make([]byte, almost1K)) +} + +func BenchmarkEncrypt8K(b *testing.B) { + benchmarkStream(b, make([]byte, almost8K)) +} + +func benchmarkSeek(b *testing.B, offset uint64) { + var key [16]byte + var iv [16]byte + + stream, _ := NewCipher(key[:], iv[:]) + + eea, ok := stream.(*eea) + if !ok { + b.Fatal("not an eea") + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + eea.reset() + eea.seek(offset) + } +} + +func BenchmarkSeek1K(b *testing.B) { + benchmarkSeek(b, 1024) +} + +func BenchmarkSeek8K(b *testing.B) { + benchmarkSeek(b, 8*1024) +} + +func BenchmarkSeek1M(b *testing.B) { + benchmarkSeek(b, 1024*1024) +} diff --git a/internal/zuc/eia.go b/internal/zuc/eia.go new file mode 100644 index 0000000..8905791 --- /dev/null +++ b/internal/zuc/eia.go @@ -0,0 +1,252 @@ +package zuc + +import ( + "fmt" + + "github.com/emmansun/gmsm/internal/byteorder" +) + +const ( + chunk = 16 +) + +type ZUC128Mac struct { + zucState32 // current zuc state + k0 [8]uint32 // keywords + t uint32 // tag + x [chunk]byte //buffer + nx int // remaining data in x + len uint64 // total data length + tagSize int // tag size + initState zucState32 // initial state for reset +} + +// NewHash create hash for zuc-128 eia, with arguments key and iv. +// Both key/iv size are 16 in bytes. +func NewHash(key, iv []byte) (*ZUC128Mac, error) { + k := len(key) + ivLen := len(iv) + mac := &ZUC128Mac{} + mac.tagSize = 4 + + switch k { + default: + return nil, fmt.Errorf("zuc: invalid key size %d, expect 16 in bytes", k) + case 16: // ZUC-128 + if ivLen != IVSize128 { + return nil, fmt.Errorf("zuc: invalid iv size %d, expect %d in bytes", ivLen, IVSize128) + } + mac.loadKeyIV16(key, iv) + } + + // initialization + for i := 0; i < 32; i++ { + mac.bitReorganization() + w := mac.f32() + mac.enterInitMode(w >> 1) + } + + // work state + mac.bitReorganization() + mac.f32() + mac.enterWorkMode() + + mac.initState.r1 = mac.r1 + mac.initState.r2 = mac.r2 + + copy(mac.initState.lfsr[:], mac.lfsr[:]) + mac.Reset() + return mac, nil +} + +func genIV4EIA(count, bearer, direction uint32) []byte { + iv := make([]byte, 16) + byteorder.BEPutUint32(iv, count) + copy(iv[9:12], iv[1:4]) + iv[4] = byte(bearer << 3) + iv[12] = iv[4] + iv[8] = iv[0] ^ byte(direction<<7) + iv[14] = byte(direction << 7) + return iv +} + +// NewEIAHash create hash for zuc-128 eia, with arguments key, count, bearer and direction +// The key must be 16 bytes long and iv must be 16 bytes long, otherwise, an error will be returned. +// The count is the 32-bit counter value, the bearer is the 5-bit bearer identity and the direction is the 1-bit +// transmission direction flag. +func NewEIAHash(key []byte, count, bearer, direction uint32) (*ZUC128Mac, error) { + return NewHash(key, genIV4EIA(count, bearer, direction)) +} + +func (m *ZUC128Mac) Size() int { + return m.tagSize +} + +func (m *ZUC128Mac) BlockSize() int { + return chunk +} + +// Reset resets the Hash to its initial state. +func (m *ZUC128Mac) Reset() { + m.t = 0 + 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.k0[:len(m.k0)/2]) +} + +func blockGeneric(m *ZUC128Mac, p []byte) { + // use 64 bits to shift left 2 keywords + var k64, t64 uint64 + t64 = uint64(m.t) << 32 + for len(p) >= chunk { + // generate next 4 keywords + m.genKeywords(m.k0[4:]) + k64 = uint64(m.k0[0])<<32 | uint64(m.k0[1]) + // process first 32 bits + w := byteorder.BEUint32(p[0:4]) + for j := 0; j < 32; j++ { + // t64 ^= (w >> 31) ? k64 : 0 + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + // process second 32 bits + k64 = uint64(m.k0[1])<<32 | uint64(m.k0[2]) + w = byteorder.BEUint32(p[4:8]) + for j := 0; j < 32; j++ { + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + // process third 32 bits + k64 = uint64(m.k0[2])<<32 | uint64(m.k0[3]) + w = byteorder.BEUint32(p[8:12]) + for j := 0; j < 32; j++ { + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + // process fourth 32 bits + k64 = uint64(m.k0[3])<<32 | uint64(m.k0[4]) + w = byteorder.BEUint32(p[12:16]) + for j := 0; j < 32; j++ { + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + // Move the new keywords to the first 4 + copy(m.k0[:4], m.k0[4:]) + p = p[chunk:] + } + m.t = uint32(t64 >> 32) +} + +func (m *ZUC128Mac) 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 { + block(m, m.x[:]) + m.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(m, p[:n]) + p = p[n:] + } + if len(p) > 0 { + m.nx = copy(m.x[:], p) + } + return +} + +func (m *ZUC128Mac) checkSum(additionalBits int, b byte) [4]byte { + if m.nx >= chunk { + panic("m.nx >= chunk") + } + kIdx := 0 + if m.nx > 0 || additionalBits > 0 { + var k64, t64 uint64 + t64 = uint64(m.t) << 32 + m.x[m.nx] = b + // total bits to handle + nRemainBits := 8*m.nx + additionalBits + if nRemainBits > 2*32 { + // generate next 2 keywords + m.genKeywords(m.k0[4:6]) + } + // nwords <= 4 + nwords := (nRemainBits + 31) / 32 + // process 32 bits at a time for first complete words + for i := 0; i < nwords-1; i++ { + k64 = uint64(m.k0[i])<<32 | uint64(m.k0[i+1]) + w := byteorder.BEUint32(m.x[i*4:]) + for j := 0; j < 32; j++ { + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + } + nRemainBits -= (nwords - 1) * 32 + // current key word index, 0 <= kIdx <= 3 + kIdx = nwords - 1 + // process remaining bits less than 32 + if nRemainBits > 0 { + k64 = uint64(m.k0[kIdx])<<32 | uint64(m.k0[kIdx+1]) + w := byteorder.BEUint32(m.x[(nwords-1)*4:]) + for j := 0; j < nRemainBits; j++ { + t64 ^= ^(uint64(w>>31) - 1) & k64 + w <<= 1 + k64 <<= 1 + } + // Reset for fianal computation + m.k0[kIdx] = uint32(k64 >> 32) // key[LENGTH] + m.k0[kIdx+1] = m.k0[kIdx+2] // Last key word + } + m.t = uint32(t64 >> 32) + } + m.t ^= m.k0[kIdx] + m.t ^= m.k0[kIdx+1] + + var digest [4]byte + byteorder.BEPutUint32(digest[:], m.t) + return digest +} + +// Finish this function hash nbits data in p and return mac value, after this function call, +// the hash state will be reset. +// In general, we will use byte level function, this is just for test/verify. +// nbits: number of bits to hash in p. +func (m *ZUC128Mac) 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) + m.Reset() + return digest[:] +} + +// Sum appends the current hash to in and returns the resulting slice. +// It does not change the underlying hash state. +func (m *ZUC128Mac) 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.go b/internal/zuc/eia256.go similarity index 100% rename from zuc/eia256.go rename to internal/zuc/eia256.go diff --git a/zuc/eia256_asm.go b/internal/zuc/eia256_asm.go similarity index 100% rename from zuc/eia256_asm.go rename to internal/zuc/eia256_asm.go diff --git a/zuc/eia256_generic.go b/internal/zuc/eia256_generic.go similarity index 100% rename from zuc/eia256_generic.go rename to internal/zuc/eia256_generic.go diff --git a/zuc/eia256_test.go b/internal/zuc/eia256_test.go similarity index 100% rename from zuc/eia256_test.go rename to internal/zuc/eia256_test.go diff --git a/zuc/eia_asm.go b/internal/zuc/eia_asm.go similarity index 100% rename from zuc/eia_asm.go rename to internal/zuc/eia_asm.go diff --git a/zuc/eia_asm_amd64.s b/internal/zuc/eia_asm_amd64.s similarity index 100% rename from zuc/eia_asm_amd64.s rename to internal/zuc/eia_asm_amd64.s diff --git a/zuc/eia_asm_arm64.s b/internal/zuc/eia_asm_arm64.s similarity index 100% rename from zuc/eia_asm_arm64.s rename to internal/zuc/eia_asm_arm64.s diff --git a/zuc/eia_asm_ppc64x.s b/internal/zuc/eia_asm_ppc64x.s similarity index 100% rename from zuc/eia_asm_ppc64x.s rename to internal/zuc/eia_asm_ppc64x.s diff --git a/zuc/eia_generic.go b/internal/zuc/eia_generic.go similarity index 100% rename from zuc/eia_generic.go rename to internal/zuc/eia_generic.go diff --git a/internal/zuc/eia_test.go b/internal/zuc/eia_test.go new file mode 100644 index 0000000..2b40946 --- /dev/null +++ b/internal/zuc/eia_test.go @@ -0,0 +1,218 @@ +package zuc + +import ( + "encoding/hex" + "hash" + "testing" + + "github.com/emmansun/gmsm/internal/byteorder" + "github.com/emmansun/gmsm/internal/cryptotest" +) + +var key [16]byte +var iv [16]byte + +var buf = make([]byte, 8192) + +func benchmarkSize(b *testing.B, size int) { + bench, _ := NewHash(key[:], iv[:]) + 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(b *testing.B) { + benchmarkSize(b, 8) +} + +func BenchmarkHash1K(b *testing.B) { + benchmarkSize(b, 1024) +} + +func BenchmarkHash8K(b *testing.B) { + benchmarkSize(b, 8192) +} + +var zucEIATests = []struct { + key []byte + count uint32 + bearer uint32 + direction uint32 + in []uint32 + nbits int + mac string +}{ + { + []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + 0, + 0, + 0, + []uint32{0x00000000}, + 1, + "c8a9595e", + }, + { + []byte{ + 0xc9, 0xe6, 0xce, 0xc4, 0x60, 0x7c, 0x72, 0xdb, + 0x00, 0x0a, 0xef, 0xa8, 0x83, 0x85, 0xab, 0x0a, + }, + 0xa94059da, + 0x0a, + 1, + []uint32{ + 0x983b41d4, 0x7d780c9e, 0x1ad11d7e, 0xb70391b1, + 0xde0b35da, 0x2dc62f83, 0xe7b78d63, 0x06ca0ea0, + 0x7e941b7b, 0xe91348f9, 0xfcb170e2, 0x217fecd9, + 0x7f9f68ad, 0xb16e5d7d, 0x21e569d2, 0x80ed775c, + 0xebde3f40, 0x93c53881, 0x00000000, + }, + 0x241, + "fae8ff0b", + }, + { + []byte{ + 0x6b, 0x8b, 0x08, 0xee, 0x79, 0xe0, 0xb5, 0x98, + 0x2d, 0x6d, 0x12, 0x8e, 0xa9, 0xf2, 0x20, 0xcb, + }, + 0x561eb2dd, + 0x1c, + 0, + []uint32{ + 0x5bad7247, 0x10ba1c56, 0xd5a315f8, 0xd40f6e09, + 0x3780be8e, 0x8de07b69, 0x92432018, 0xe08ed96a, + 0x5734af8b, 0xad8a575d, 0x3a1f162f, 0x85045cc7, + 0x70925571, 0xd9f5b94e, 0x454a77c1, 0x6e72936b, + 0xf016ae15, 0x7499f054, 0x3b5d52ca, 0xa6dbeab6, + 0x97d2bb73, 0xe41b8075, 0xdce79b4b, 0x86044f66, + 0x1d4485a5, 0x43dd7860, 0x6e0419e8, 0x059859d3, + 0xcb2b67ce, 0x0977603f, 0x81ff839e, 0x33185954, + 0x4cfbc8d0, 0x0fef1a4c, 0x8510fb54, 0x7d6b06c6, + 0x11ef44f1, 0xbce107cf, 0xa45a06aa, 0xb360152b, + 0x28dc1ebe, 0x6f7fe09b, 0x0516f9a5, 0xb02a1bd8, + 0x4bb0181e, 0x2e89e19b, 0xd8125930, 0xd178682f, + 0x3862dc51, 0xb636f04e, 0x720c47c3, 0xce51ad70, + 0xd94b9b22, 0x55fbae90, 0x6549f499, 0xf8c6d399, + 0x47ed5e5d, 0xf8e2def1, 0x13253e7b, 0x08d0a76b, + 0x6bfc68c8, 0x12f375c7, 0x9b8fe5fd, 0x85976aa6, + 0xd46b4a23, 0x39d8ae51, 0x47f680fb, 0xe70f978b, + 0x38effd7b, 0x2f7866a2, 0x2554e193, 0xa94e98a6, + 0x8b74bd25, 0xbb2b3f5f, 0xb0a5fd59, 0x887f9ab6, + 0x8159b717, 0x8d5b7b67, 0x7cb546bf, 0x41eadca2, + 0x16fc1085, 0x0128f8bd, 0xef5c8d89, 0xf96afa4f, + 0xa8b54885, 0x565ed838, 0xa950fee5, 0xf1c3b0a4, + 0xf6fb71e5, 0x4dfd169e, 0x82cecc72, 0x66c850e6, + 0x7c5ef0ba, 0x960f5214, 0x060e71eb, 0x172a75fc, + 0x1486835c, 0xbea65344, 0x65b055c9, 0x6a72e410, + 0x52241823, 0x25d83041, 0x4b40214d, 0xaa8091d2, + 0xe0fb010a, 0xe15c6de9, 0x0850973b, 0xdf1e423b, + 0xe148a237, 0xb87a0c9f, 0x34d4b476, 0x05b803d7, + 0x43a86a90, 0x399a4af3, 0x96d3a120, 0x0a62f3d9, + 0x507962e8, 0xe5bee6d3, 0xda2bb3f7, 0x237664ac, + 0x7a292823, 0x900bc635, 0x03b29e80, 0xd63f6067, + 0xbf8e1716, 0xac25beba, 0x350deb62, 0xa99fe031, + 0x85eb4f69, 0x937ecd38, 0x7941fda5, 0x44ba67db, + 0x09117749, 0x38b01827, 0xbcc69c92, 0xb3f772a9, + 0xd2859ef0, 0x03398b1f, 0x6bbad7b5, 0x74f7989a, + 0x1d10b2df, 0x798e0dbf, 0x30d65874, 0x64d24878, + 0xcd00c0ea, 0xee8a1a0c, 0xc753a279, 0x79e11b41, + 0xdb1de3d5, 0x038afaf4, 0x9f5c682c, 0x3748d8a3, + 0xa9ec54e6, 0xa371275f, 0x1683510f, 0x8e4f9093, + 0x8f9ab6e1, 0x34c2cfdf, 0x4841cba8, 0x8e0cff2b, + 0x0bcc8e6a, 0xdcb71109, 0xb5198fec, 0xf1bb7e5c, + 0x531aca50, 0xa56a8a3b, 0x6de59862, 0xd41fa113, + 0xd9cd9578, 0x08f08571, 0xd9a4bb79, 0x2af271f6, + 0xcc6dbb8d, 0xc7ec36e3, 0x6be1ed30, 0x8164c31c, + 0x7c0afc54, 0x1c000000, + }, + 0x1626, + "0ca12792", + }, +} + +func TestEIA_Finish(t *testing.T) { + for i, test := range zucEIATests { + h, err := NewEIAHash(test.key, test.count, test.bearer, test.direction) + if err != nil { + t.Error(err) + } + in := make([]byte, len(test.in)*4) + for j, v := range test.in { + byteorder.BEPutUint32(in[j*4:], v) + } + + mac := h.Finish(in, test.nbits) + if hex.EncodeToString(mac) != test.mac { + t.Errorf("case %d, expected=%s, result=%s\n", i+1, test.mac, hex.EncodeToString(mac)) + } + } +} + +func TestEIA_NewHash(t *testing.T) { + key := make([]byte, 16) + iv := make([]byte, 16) + _, err := NewHash(key[:1], iv) + if err == nil { + t.Fatal("error is expected") + } + + _, err = NewHash(key, iv[:1]) + if err == nil { + t.Fatal("error is expected") + } + + h, err := NewHash(key, iv) + if err != nil { + t.Fatal(err) + } + if h.Size() != 4 { + t.Fatal("eia3 mac size should be 4 bytes") + } + if h.BlockSize() != 16 { + t.Fatal("current eia3 implementation's block size should be 16 bytes") + } + +} + +func TestEIA_Sum(t *testing.T) { + expected := "6c2db416" + h, err := NewEIAHash(zucEIATests[1].key, zucEIATests[1].count, zucEIATests[1].bearer, zucEIATests[1].direction) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("shangmi1")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun shangmi")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun shangmi 1234")) + if err != nil { + t.Fatal(err) + } + mac := h.Sum(nil) + if hex.EncodeToString(mac) != expected { + t.Errorf("expected=%s, result=%s\n", expected, hex.EncodeToString(mac)) + } +} + +func TestEIAHash(t *testing.T) { + t.Run("EIA-128", func(t *testing.T) { + cryptotest.TestHash(t, func() hash.Hash { + h, _ := NewEIAHash(zucEIATests[0].key, zucEIATests[0].count, zucEIATests[0].bearer, zucEIATests[0].direction) + return h + }) + }) +} diff --git a/zuc/eea.go b/zuc/eea.go index 2fe8204..b5682aa 100644 --- a/zuc/eea.go +++ b/zuc/eea.go @@ -1,13 +1,16 @@ +// Package zuc implements ShangMi(SM) zuc stream cipher and integrity algorithm. package zuc import ( "github.com/emmansun/gmsm/cipher" - "github.com/emmansun/gmsm/internal/alias" - "github.com/emmansun/gmsm/internal/byteorder" - "github.com/emmansun/gmsm/internal/subtle" + "github.com/emmansun/gmsm/internal/zuc" ) const ( + // IV size in bytes for zuc 128 + IVSize128 = 16 + // IV size in bytes for zuc 256 + IVSize256 = 23 // number of words in a round RoundWords = 32 // number of bytes in a word @@ -17,28 +20,12 @@ const ( RoundBytes = RoundWords * WordSize ) -type eea struct { - zucState32 - x [WordSize]byte // remaining bytes buffer - xLen int // number of remaining bytes - initState zucState32 // initial state for reset - used uint64 // number of key bytes processed, current offset -} - // NewCipher create a stream cipher based on key and iv aguments. // The key must be 16 bytes long and iv must be 16 bytes long for zuc 128; // or the key must be 32 bytes long and iv must be 23 bytes long for zuc 256; // otherwise, an error will be returned. func NewCipher(key, iv []byte) (cipher.SeekableStream, error) { - s, err := newZUCState(key, iv) - if err != nil { - return nil, err - } - c := new(eea) - c.zucState32 = *s - c.initState = *s - c.used = 0 - return c, nil + return zuc.NewCipher(key, iv) } // NewEEACipher create a stream cipher based on key, count, bearer and direction arguments according specification. @@ -46,122 +33,5 @@ func NewCipher(key, iv []byte) (cipher.SeekableStream, error) { // The count is the 32-bit counter value, the bearer is the 5-bit bearer identity and the direction is the 1-bit // transmission direction flag. func NewEEACipher(key []byte, count, bearer, direction uint32) (cipher.SeekableStream, error) { - iv := make([]byte, 16) - byteorder.BEPutUint32(iv, count) - copy(iv[8:12], iv[:4]) - iv[4] = byte(((bearer << 1) | (direction & 1)) << 2) - iv[12] = iv[4] - return NewCipher(key, iv) -} - -func genKeyStreamRev32Generic(keyStream []byte, pState *zucState32) { - for len(keyStream) >= WordSize { - z := genKeyword(pState) - byteorder.BEPutUint32(keyStream, z) - keyStream = keyStream[WordSize:] - } -} - -func (c *eea) XORKeyStream(dst, src []byte) { - if len(dst) < len(src) { - panic("zuc: output smaller than input") - } - if alias.InexactOverlap(dst[:len(src)], src) { - panic("zuc: invalid buffer overlap") - } - used := len(src) - if c.xLen > 0 { - // handle remaining key bytes - n := subtle.XORBytes(dst, src, c.x[:c.xLen]) - c.xLen -= n - dst = dst[n:] - src = src[n:] - if c.xLen > 0 { - copy(c.x[:], c.x[n:c.xLen+n]) - c.used += uint64(used) - return - } - } - var keyBytes [RoundBytes]byte - for len(src) >= RoundBytes { - genKeyStreamRev32(keyBytes[:], &c.zucState32) - subtle.XORBytes(dst, src, keyBytes[:]) - dst = dst[RoundBytes:] - src = src[RoundBytes:] - } - if len(src) > 0 { - byteLen := (len(src) + WordMask) &^ WordMask - genKeyStreamRev32(keyBytes[:byteLen], &c.zucState32) - n := subtle.XORBytes(dst, src, keyBytes[:]) - // save remaining key bytes - c.xLen = byteLen - n - if c.xLen > 0 { - copy(c.x[:], keyBytes[n:byteLen]) - } - } - c.used += uint64(used) -} - -func (c *eea) reset() { - c.zucState32 = c.initState - c.xLen = 0 - c.used = 0 -} - -// seek sets the offset for the next XORKeyStream operation. -// -// If the offset is less than the current offset, the state will be reset to the initial state. -// If the offset is equal to the current offset, the function behaves the same as XORKeyStream. -// If the offset is greater than the current offset, the function will forward the state to the offset. -// Note: This method is not thread-safe. -func (c *eea) seek(offset uint64) { - if offset < c.used { - c.reset() - } - if offset == c.used { - return - } - gap := offset - c.used - if gap <= uint64(c.xLen) { - // offset is within the remaining key bytes - c.xLen -= int(gap) - c.used += gap - if c.xLen > 0 { - // adjust remaining key bytes - copy(c.x[:], c.x[gap:]) - } - return - } - // consumed all remaining key bytes first - if c.xLen > 0 { - c.used += uint64(c.xLen) - gap -= uint64(c.xLen) - c.xLen = 0 - } - - // forward the state to the offset - c.used += gap - stepLen := uint64(RoundBytes) - var keyStream [RoundWords]uint32 - for gap >= stepLen { - genKeyStream(keyStream[:], &c.zucState32) - gap -= stepLen - } - - if gap > 0 { - numWords := (gap + WordMask) / WordSize - genKeyStream(keyStream[:numWords], &c.zucState32) - partiallyUsed := int(gap & WordMask) - if partiallyUsed > 0 { - // save remaining key bytes (less than 4 bytes) - c.xLen = WordSize - partiallyUsed - byteorder.BEPutUint32(c.x[:], keyStream[numWords-1]) - copy(c.x[:], c.x[partiallyUsed:]) - } - } -} - -func (c *eea) XORKeyStreamAt(dst, src []byte, offset uint64) { - c.seek(offset) - c.XORKeyStream(dst, src) + return zuc.NewEEACipher(key, count, bearer, direction) } diff --git a/zuc/eea_test.go b/zuc/eea_test.go index a192831..e979096 100644 --- a/zuc/eea_test.go +++ b/zuc/eea_test.go @@ -227,33 +227,3 @@ func BenchmarkEncrypt1K(b *testing.B) { func BenchmarkEncrypt8K(b *testing.B) { benchmarkStream(b, make([]byte, almost8K)) } - -func benchmarkSeek(b *testing.B, offset uint64) { - var key [16]byte - var iv [16]byte - - stream, _ := NewCipher(key[:], iv[:]) - - eea, ok := stream.(*eea) - if !ok { - b.Fatal("not an eea") - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - eea.reset() - eea.seek(offset) - } -} - -func BenchmarkSeek1K(b *testing.B) { - benchmarkSeek(b, 1024) -} - -func BenchmarkSeek8K(b *testing.B) { - benchmarkSeek(b, 8*1024) -} - -func BenchmarkSeek1M(b *testing.B) { - benchmarkSeek(b, 1024*1024) -} diff --git a/zuc/eia.go b/zuc/eia.go index 8905791..2b046e8 100644 --- a/zuc/eia.go +++ b/zuc/eia.go @@ -1,252 +1,36 @@ +// Package zuc provides implementations of the ZUC stream cipher and its related +// cryptographic functions. package zuc import ( - "fmt" + "hash" - "github.com/emmansun/gmsm/internal/byteorder" + "github.com/emmansun/gmsm/internal/zuc" ) -const ( - chunk = 16 -) -type ZUC128Mac struct { - zucState32 // current zuc state - k0 [8]uint32 // keywords - t uint32 // tag - x [chunk]byte //buffer - nx int // remaining data in x - len uint64 // total data length - tagSize int // tag size - initState zucState32 // initial state for reset +// EIA is an interface that extends the hash.Hash interface with an additional +// Finish method, which finalizes the hash computation with a specified number +// of bits. +type EIA interface { + hash.Hash + Finish(p []byte, nbits int) []byte } -// NewHash create hash for zuc-128 eia, with arguments key and iv. -// Both key/iv size are 16 in bytes. -func NewHash(key, iv []byte) (*ZUC128Mac, error) { - k := len(key) - ivLen := len(iv) - mac := &ZUC128Mac{} - mac.tagSize = 4 - - switch k { - default: - return nil, fmt.Errorf("zuc: invalid key size %d, expect 16 in bytes", k) - case 16: // ZUC-128 - if ivLen != IVSize128 { - return nil, fmt.Errorf("zuc: invalid iv size %d, expect %d in bytes", ivLen, IVSize128) - } - mac.loadKeyIV16(key, iv) - } - - // initialization - for i := 0; i < 32; i++ { - mac.bitReorganization() - w := mac.f32() - mac.enterInitMode(w >> 1) - } - - // work state - mac.bitReorganization() - mac.f32() - mac.enterWorkMode() - - mac.initState.r1 = mac.r1 - mac.initState.r2 = mac.r2 - - copy(mac.initState.lfsr[:], mac.lfsr[:]) - mac.Reset() - return mac, nil +// NewHash creates a new instance of the ZUC-based hash function with the given +// key and initialization vector (IV). +func NewHash(key, iv []byte) (EIA, error) { + return zuc.NewHash(key, iv) } -func genIV4EIA(count, bearer, direction uint32) []byte { - iv := make([]byte, 16) - byteorder.BEPutUint32(iv, count) - copy(iv[9:12], iv[1:4]) - iv[4] = byte(bearer << 3) - iv[12] = iv[4] - iv[8] = iv[0] ^ byte(direction<<7) - iv[14] = byte(direction << 7) - return iv +// NewEIAHash creates a new instance of the ZUC-based EIA (Encryption Integrity +// Algorithm) hash function with the given key, count, bearer, and direction. +func NewEIAHash(key []byte, count, bearer, direction uint32) (EIA, error) { + return zuc.NewEIAHash(key, count, bearer, direction) } -// NewEIAHash create hash for zuc-128 eia, with arguments key, count, bearer and direction -// The key must be 16 bytes long and iv must be 16 bytes long, otherwise, an error will be returned. -// The count is the 32-bit counter value, the bearer is the 5-bit bearer identity and the direction is the 1-bit -// transmission direction flag. -func NewEIAHash(key []byte, count, bearer, direction uint32) (*ZUC128Mac, error) { - return NewHash(key, genIV4EIA(count, bearer, direction)) -} - -func (m *ZUC128Mac) Size() int { - return m.tagSize -} - -func (m *ZUC128Mac) BlockSize() int { - return chunk -} - -// Reset resets the Hash to its initial state. -func (m *ZUC128Mac) Reset() { - m.t = 0 - 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.k0[:len(m.k0)/2]) -} - -func blockGeneric(m *ZUC128Mac, p []byte) { - // use 64 bits to shift left 2 keywords - var k64, t64 uint64 - t64 = uint64(m.t) << 32 - for len(p) >= chunk { - // generate next 4 keywords - m.genKeywords(m.k0[4:]) - k64 = uint64(m.k0[0])<<32 | uint64(m.k0[1]) - // process first 32 bits - w := byteorder.BEUint32(p[0:4]) - for j := 0; j < 32; j++ { - // t64 ^= (w >> 31) ? k64 : 0 - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - // process second 32 bits - k64 = uint64(m.k0[1])<<32 | uint64(m.k0[2]) - w = byteorder.BEUint32(p[4:8]) - for j := 0; j < 32; j++ { - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - // process third 32 bits - k64 = uint64(m.k0[2])<<32 | uint64(m.k0[3]) - w = byteorder.BEUint32(p[8:12]) - for j := 0; j < 32; j++ { - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - // process fourth 32 bits - k64 = uint64(m.k0[3])<<32 | uint64(m.k0[4]) - w = byteorder.BEUint32(p[12:16]) - for j := 0; j < 32; j++ { - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - // Move the new keywords to the first 4 - copy(m.k0[:4], m.k0[4:]) - p = p[chunk:] - } - m.t = uint32(t64 >> 32) -} - -func (m *ZUC128Mac) 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 { - block(m, m.x[:]) - m.nx = 0 - } - p = p[n:] - } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(m, p[:n]) - p = p[n:] - } - if len(p) > 0 { - m.nx = copy(m.x[:], p) - } - return -} - -func (m *ZUC128Mac) checkSum(additionalBits int, b byte) [4]byte { - if m.nx >= chunk { - panic("m.nx >= chunk") - } - kIdx := 0 - if m.nx > 0 || additionalBits > 0 { - var k64, t64 uint64 - t64 = uint64(m.t) << 32 - m.x[m.nx] = b - // total bits to handle - nRemainBits := 8*m.nx + additionalBits - if nRemainBits > 2*32 { - // generate next 2 keywords - m.genKeywords(m.k0[4:6]) - } - // nwords <= 4 - nwords := (nRemainBits + 31) / 32 - // process 32 bits at a time for first complete words - for i := 0; i < nwords-1; i++ { - k64 = uint64(m.k0[i])<<32 | uint64(m.k0[i+1]) - w := byteorder.BEUint32(m.x[i*4:]) - for j := 0; j < 32; j++ { - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - } - nRemainBits -= (nwords - 1) * 32 - // current key word index, 0 <= kIdx <= 3 - kIdx = nwords - 1 - // process remaining bits less than 32 - if nRemainBits > 0 { - k64 = uint64(m.k0[kIdx])<<32 | uint64(m.k0[kIdx+1]) - w := byteorder.BEUint32(m.x[(nwords-1)*4:]) - for j := 0; j < nRemainBits; j++ { - t64 ^= ^(uint64(w>>31) - 1) & k64 - w <<= 1 - k64 <<= 1 - } - // Reset for fianal computation - m.k0[kIdx] = uint32(k64 >> 32) // key[LENGTH] - m.k0[kIdx+1] = m.k0[kIdx+2] // Last key word - } - m.t = uint32(t64 >> 32) - } - m.t ^= m.k0[kIdx] - m.t ^= m.k0[kIdx+1] - - var digest [4]byte - byteorder.BEPutUint32(digest[:], m.t) - return digest -} - -// Finish this function hash nbits data in p and return mac value, after this function call, -// the hash state will be reset. -// In general, we will use byte level function, this is just for test/verify. -// nbits: number of bits to hash in p. -func (m *ZUC128Mac) 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) - m.Reset() - return digest[:] -} - -// Sum appends the current hash to in and returns the resulting slice. -// It does not change the underlying hash state. -func (m *ZUC128Mac) 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[:]...) +// NewHash256 creates a new instance of the ZUC256-based hash function with the +// given key, initialization vector (IV), and tag size. +func NewHash256(key, iv []byte, tagSize int) (EIA, error) { + return zuc.NewHash256(key, iv, tagSize) } diff --git a/zuc/eia_test.go b/zuc/eia_test.go index 2b40946..f2f1f49 100644 --- a/zuc/eia_test.go +++ b/zuc/eia_test.go @@ -216,3 +216,329 @@ func TestEIAHash(t *testing.T) { }) }) } + +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 TestEIA_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 TestEIA_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 TestEIA_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 TestEIA256_Sum32(t *testing.T) { + expected := "f4f20d7c" + h, err := NewHash256(zucEIA256Tests[2].key, zucEIA256Tests[2].iv, 4) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("shangmi1")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun shangmi")) + if err != nil { + t.Fatal(err) + } + _, err = h.Write([]byte("emmansun shangmi 1234")) + if err != nil { + t.Fatal(err) + } + mac := h.Sum(nil) + if hex.EncodeToString(mac) != expected { + t.Errorf("expected=%s, result=%s\n", expected, hex.EncodeToString(mac)) + } +} + +func TestEIA256_Finish(t *testing.T) { + expected := []struct { + expected string + macLen int + }{ + { + "9dd592c4", + 4, + }, + { + "1f6f71e386a2ce01", + 8, + }, + { + "bf5339cfd87bba97d70ef4f5973af8bb", + 16, + }, + } + for _, exp := range expected { + h, err := NewHash256(zucEIA256Tests[2].key, zucEIA256Tests[2].iv, exp.macLen) + if err != nil { + t.Fatal(err) + } + mac := h.Finish([]byte("emmansunshangmi1emmansun shangmiemmansun shangmi 12345"), 8*53+4) + if hex.EncodeToString(mac) != exp.expected { + t.Errorf("expected=%s, result=%s\n", exp.expected, hex.EncodeToString(mac)) + } + } +} + +func TestEIA256Hash(t *testing.T) { + t.Run("EIA-256-32", func(t *testing.T) { + cryptotest.TestHash(t, func() hash.Hash { + h, _ := NewHash256(zucEIA256Tests[0].key, zucEIA256Tests[0].iv, 4) + return h + }) + }) + t.Run("EIA-256-64", func(t *testing.T) { + cryptotest.TestHash(t, func() hash.Hash { + h, _ := NewHash256(zucEIA256Tests[0].key, zucEIA256Tests[0].iv, 8) + return h + }) + }) + t.Run("EIA-256-128", func(t *testing.T) { + cryptotest.TestHash(t, func() hash.Hash { + h, _ := NewHash256(zucEIA256Tests[0].key, zucEIA256Tests[0].iv, 16) + return h + }) + }) +} + +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) +} diff --git a/zuc/example_test.go b/zuc/example_test.go index 525ad64..5374123 100644 --- a/zuc/example_test.go +++ b/zuc/example_test.go @@ -167,7 +167,7 @@ func ExampleNewHash256_tagSize16() { // Output: fd8d10ea65b6369cccc07d50b4657d84 } -func ExampleZUC128Mac_Finish() { +func ExampleEIA_Finish() { key := make([]byte, 16) iv := make([]byte, 16) h, err := zuc.NewHash(key, iv) @@ -178,7 +178,7 @@ func ExampleZUC128Mac_Finish() { // Output: c8a9595e } -func ExampleZUC128Mac_Finish_mixed() { +func ExampleEIA_Finish_mixed() { key := []byte{ 0xc9, 0xe6, 0xce, 0xc4, 0x60, 0x7c, 0x72, 0xdb, 0x00, 0x0a, 0xef, 0xa8, 0x83, 0x85, 0xab, 0x0a,