From a5d489ee59a7b8c167cbc38fea8ef0c3a715d419 Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Mon, 5 Aug 2024 11:40:04 +0800 Subject: [PATCH] internal/cryptotest: add tests for the cipher.Stream interface --- cipher/cfb_sm4_test.go | 22 ++++ cipher/ctr_sm4_test.go | 81 +++++++----- cipher/ofb_sm4_test.go | 17 +++ internal/cryptotest/stream.go | 242 ++++++++++++++++++++++++++++++++++ zuc/eea.go | 47 ++++++- zuc/eea_test.go | 11 ++ zuc/eia256_test.go | 24 ++++ zuc/eia_test.go | 12 ++ 8 files changed, 418 insertions(+), 38 deletions(-) create mode 100644 internal/cryptotest/stream.go diff --git a/cipher/cfb_sm4_test.go b/cipher/cfb_sm4_test.go index bcff933..f2a0577 100644 --- a/cipher/cfb_sm4_test.go +++ b/cipher/cfb_sm4_test.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "testing" + "github.com/emmansun/gmsm/internal/cryptotest" "github.com/emmansun/gmsm/sm4" ) @@ -105,3 +106,24 @@ func TestCFBInverse(t *testing.T) { t.Errorf("got: %x, want: %x", plaintextCopy, plaintext) } } + +func TestCFBStream(t *testing.T) { + t.Run("SM4", func(t *testing.T) { + rng := newRandReader(t) + + key := make([]byte, 16) + rng.Read(key) + + block, err := sm4.NewCipher(key) + if err != nil { + panic(err) + } + + t.Run("Encrypter", func(t *testing.T) { + cryptotest.TestStreamFromBlock(t, block, cipher.NewCFBEncrypter) + }) + t.Run("Decrypter", func(t *testing.T) { + cryptotest.TestStreamFromBlock(t, block, cipher.NewCFBDecrypter) + }) + }) +} diff --git a/cipher/ctr_sm4_test.go b/cipher/ctr_sm4_test.go index 2fa7e34..0050329 100644 --- a/cipher/ctr_sm4_test.go +++ b/cipher/ctr_sm4_test.go @@ -5,6 +5,7 @@ import ( "crypto/cipher" "testing" + "github.com/emmansun/gmsm/internal/cryptotest" "github.com/emmansun/gmsm/sm4" ) @@ -56,12 +57,12 @@ var ctrSM4Tests = []struct { 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, }, []byte{ - 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, - 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, - 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, - 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, - 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, - 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, + 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, + 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, + 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, + 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, + 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, + 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, }, }, { @@ -79,14 +80,14 @@ var ctrSM4Tests = []struct { 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, }, []byte{ - 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, - 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, - 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, - 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, - 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, - 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, - 0x41, 0xb7, 0x3a, 0x57, 0x17, 0x65, 0xef, 0x3d, 0xbb, 0xd1, 0x04, 0x5d, 0xb7, 0xf8, 0x71, 0x39, - 0xff, 0x82, 0x01, 0x19, 0x75, 0xaf, 0x8a, 0x01, 0x8a, 0x0a, 0x26, 0x93, 0x85, 0xfd, 0x04, 0x5f, + 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, + 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, + 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, + 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, + 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, + 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, + 0x41, 0xb7, 0x3a, 0x57, 0x17, 0x65, 0xef, 0x3d, 0xbb, 0xd1, 0x04, 0x5d, 0xb7, 0xf8, 0x71, 0x39, + 0xff, 0x82, 0x01, 0x19, 0x75, 0xaf, 0x8a, 0x01, 0x8a, 0x0a, 0x26, 0x93, 0x85, 0xfd, 0x04, 0x5f, }, }, { @@ -109,27 +110,27 @@ var ctrSM4Tests = []struct { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, - 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, }, []byte{ - 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, - 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, - 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, - 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, - 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, - 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, - 0x41, 0xb7, 0x3a, 0x57, 0x17, 0x65, 0xef, 0x3d, 0xbb, 0xd1, 0x04, 0x5d, 0xb7, 0xf8, 0x71, 0x39, - 0xff, 0x82, 0x01, 0x19, 0x75, 0xaf, 0x8a, 0x01, 0x8a, 0x0a, 0x26, 0x93, 0x85, 0xfd, 0x04, 0x5f, - 0x73, 0xd4, 0xc6, 0x3a, 0x81, 0x0a, 0x91, 0xe3, 0xb9, 0x17, 0x89, 0xdf, 0x4c, 0xcd, 0xe8, 0xe3, - 0x4e, 0xe7, 0x8d, 0x52, 0x89, 0x93, 0xb9, 0xef, 0x42, 0xe7, 0x5d, 0x67, 0xa8, 0x25, 0xad, 0xf0, - 0xe2, 0x45, 0x9d, 0x8c, 0x30, 0x61, 0x8a, 0x26, 0x90, 0x4f, 0x52, 0x61, 0xa0, 0x61, 0x62, 0xfb, - 0x36, 0xc8, 0x95, 0xe2, 0x8d, 0x75, 0x86, 0xf5, 0xbf, 0x22, 0x1c, 0xdd, 0xc9, 0x52, 0x71, 0x5a, - 0x7e, 0xb0, 0x56, 0xd6, 0x8a, 0x7e, 0xfa, 0x4f, 0xda, 0x6b, 0x97, 0x95, 0x23, 0xa7, 0xa8, 0x39, - 0x76, 0x31, 0x10, 0x79, 0x47, 0x98, 0x5b, 0x71, 0xbf, 0xc9, 0x4c, 0xce, 0xb7, 0xd4, 0x19, 0x86, - 0x04, 0x87, 0xc0, 0xba, 0xe8, 0xa5, 0x4c, 0xc8, 0x48, 0x9c, 0x28, 0xd3, 0x4b, 0x4d, 0xfc, 0x3f, - 0x9b, 0xbc, 0xf3, 0xd1, 0x9d, 0x25, 0x43, 0x47, 0x37, 0xea, 0xb7, 0x5e, 0x0d, 0xdb, 0x58, 0xf1, + 0xbc, 0x71, 0x0d, 0x76, 0x2d, 0x07, 0x0b, 0x26, 0x36, 0x1d, 0xa8, 0x2b, 0x54, 0x56, 0x5e, 0x46, + 0xb0, 0x2b, 0x3d, 0xbd, 0xdd, 0x50, 0xd5, 0xb4, 0x58, 0xae, 0xcc, 0xb2, 0x5d, 0xa1, 0x05, 0xe1, + 0x6a, 0xd7, 0x0b, 0xc0, 0x11, 0x75, 0xad, 0x43, 0xb0, 0x80, 0x6a, 0x2e, 0x7b, 0x9c, 0xa5, 0x45, + 0x60, 0x24, 0x59, 0xa0, 0x6b, 0x7d, 0x13, 0x0d, 0xde, 0x42, 0xa3, 0xe0, 0x47, 0x68, 0x18, 0xd2, + 0x00, 0xb8, 0x33, 0x1a, 0x66, 0x57, 0xd6, 0xbe, 0xb8, 0x5b, 0x72, 0x4f, 0x55, 0x0c, 0xd5, 0x2d, + 0x96, 0xf3, 0xe4, 0x12, 0x37, 0xa2, 0x07, 0x44, 0x43, 0xa5, 0x43, 0x3a, 0x41, 0x33, 0x0d, 0xca, + 0x41, 0xb7, 0x3a, 0x57, 0x17, 0x65, 0xef, 0x3d, 0xbb, 0xd1, 0x04, 0x5d, 0xb7, 0xf8, 0x71, 0x39, + 0xff, 0x82, 0x01, 0x19, 0x75, 0xaf, 0x8a, 0x01, 0x8a, 0x0a, 0x26, 0x93, 0x85, 0xfd, 0x04, 0x5f, + 0x73, 0xd4, 0xc6, 0x3a, 0x81, 0x0a, 0x91, 0xe3, 0xb9, 0x17, 0x89, 0xdf, 0x4c, 0xcd, 0xe8, 0xe3, + 0x4e, 0xe7, 0x8d, 0x52, 0x89, 0x93, 0xb9, 0xef, 0x42, 0xe7, 0x5d, 0x67, 0xa8, 0x25, 0xad, 0xf0, + 0xe2, 0x45, 0x9d, 0x8c, 0x30, 0x61, 0x8a, 0x26, 0x90, 0x4f, 0x52, 0x61, 0xa0, 0x61, 0x62, 0xfb, + 0x36, 0xc8, 0x95, 0xe2, 0x8d, 0x75, 0x86, 0xf5, 0xbf, 0x22, 0x1c, 0xdd, 0xc9, 0x52, 0x71, 0x5a, + 0x7e, 0xb0, 0x56, 0xd6, 0x8a, 0x7e, 0xfa, 0x4f, 0xda, 0x6b, 0x97, 0x95, 0x23, 0xa7, 0xa8, 0x39, + 0x76, 0x31, 0x10, 0x79, 0x47, 0x98, 0x5b, 0x71, 0xbf, 0xc9, 0x4c, 0xce, 0xb7, 0xd4, 0x19, 0x86, + 0x04, 0x87, 0xc0, 0xba, 0xe8, 0xa5, 0x4c, 0xc8, 0x48, 0x9c, 0x28, 0xd3, 0x4b, 0x4d, 0xfc, 0x3f, + 0x9b, 0xbc, 0xf3, 0xd1, 0x9d, 0x25, 0x43, 0x47, 0x37, 0xea, 0xb7, 0x5e, 0x0d, 0xdb, 0x58, 0xf1, }, - }, + }, } func TestCTR_SM4(t *testing.T) { @@ -167,3 +168,19 @@ func TestCTR_SM4(t *testing.T) { } } } + +func TestCTRStream(t *testing.T) { + t.Run("SM4", func(t *testing.T) { + rng := newRandReader(t) + + key := make([]byte, 16) + rng.Read(key) + + block, err := sm4.NewCipher(key) + if err != nil { + panic(err) + } + + cryptotest.TestStreamFromBlock(t, block, cipher.NewCTR) + }) +} diff --git a/cipher/ofb_sm4_test.go b/cipher/ofb_sm4_test.go index dff540d..8893ccc 100644 --- a/cipher/ofb_sm4_test.go +++ b/cipher/ofb_sm4_test.go @@ -5,6 +5,7 @@ import ( "crypto/cipher" "testing" + "github.com/emmansun/gmsm/internal/cryptotest" "github.com/emmansun/gmsm/sm4" ) @@ -71,3 +72,19 @@ func TestOFB(t *testing.T) { } } } + +func TestOFBStream(t *testing.T) { + t.Run("SM4", func(t *testing.T) { + rng := newRandReader(t) + + key := make([]byte, 16) + rng.Read(key) + + block, err := sm4.NewCipher(key) + if err != nil { + panic(err) + } + + cryptotest.TestStreamFromBlock(t, block, cipher.NewOFB) + }) +} diff --git a/internal/cryptotest/stream.go b/internal/cryptotest/stream.go new file mode 100644 index 0000000..f4191d6 --- /dev/null +++ b/internal/cryptotest/stream.go @@ -0,0 +1,242 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptotest + +import ( + "bytes" + "crypto/cipher" + "fmt" + "strings" + "testing" + + "github.com/emmansun/gmsm/internal/subtle" +) + +// Each test is executed with each of the buffer lengths in bufLens. +var ( + bufLens = []int{0, 1, 3, 4, 8, 10, 15, 16, 20, 32, 50, 4096, 5000} + bufCap = 10000 +) + +// MakeStream returns a cipher.Stream instance. +// +// Multiple calls to MakeStream must return equivalent instances, +// so for example the key and/or IV must be fixed. +type MakeStream func() cipher.Stream + +// TestStream performs a set of tests on cipher.Stream implementations, +// checking the documented requirements of XORKeyStream. +func TestStream(t *testing.T, ms MakeStream) { + + t.Run("XORSemantics", func(t *testing.T) { + if strings.Contains(t.Name(), "TestCFBStream") { + // This is ugly, but so is CFB's abuse of cipher.Stream. + // Don't want to make it easier for anyone else to do that. + t.Skip("CFB implements cipher.Stream but does not follow XOR semantics") + } + + // Test that XORKeyStream inverts itself for encryption/decryption. + t.Run("Roundtrip", func(t *testing.T) { + + for _, length := range bufLens { + t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) { + rng := newRandReader(t) + + plaintext := make([]byte, length) + rng.Read(plaintext) + + ciphertext := make([]byte, length) + decrypted := make([]byte, length) + + ms().XORKeyStream(ciphertext, plaintext) // Encrypt plaintext + ms().XORKeyStream(decrypted, ciphertext) // Decrypt ciphertext + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %s, want %s", truncateHex(decrypted), truncateHex(plaintext)) + } + }) + } + }) + + // Test that XORKeyStream behaves the same as directly XORing + // plaintext with the stream. + t.Run("DirectXOR", func(t *testing.T) { + + for _, length := range bufLens { + t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) { + rng := newRandReader(t) + + plaintext := make([]byte, length) + rng.Read(plaintext) + + // Encrypting all zeros should reveal the stream itself + stream, directXOR := make([]byte, length), make([]byte, length) + ms().XORKeyStream(stream, stream) + // Encrypt plaintext by directly XORing the stream + subtle.XORBytes(directXOR, stream, plaintext) + + // Encrypt plaintext with XORKeyStream + ciphertext := make([]byte, length) + ms().XORKeyStream(ciphertext, plaintext) + if !bytes.Equal(ciphertext, directXOR) { + t.Errorf("xor semantics were not preserved; got %s, want %s", truncateHex(ciphertext), truncateHex(directXOR)) + } + }) + } + }) + }) + + t.Run("AlterInput", func(t *testing.T) { + rng := newRandReader(t) + src, dst, before := make([]byte, bufCap), make([]byte, bufCap), make([]byte, bufCap) + rng.Read(src) + + for _, length := range bufLens { + + t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) { + copy(before, src) + + ms().XORKeyStream(dst[:length], src[:length]) + if !bytes.Equal(src, before) { + t.Errorf("XORKeyStream modified src; got %s, want %s", truncateHex(src), truncateHex(before)) + } + }) + } + }) + + t.Run("Aliasing", func(t *testing.T) { + rng := newRandReader(t) + + buff, expectedOutput := make([]byte, bufCap), make([]byte, bufCap) + + for _, length := range bufLens { + // Record what output is when src and dst are different + rng.Read(buff) + ms().XORKeyStream(expectedOutput[:length], buff[:length]) + + // Check that the same output is generated when src=dst alias to the same + // memory + ms().XORKeyStream(buff[:length], buff[:length]) + if !bytes.Equal(buff[:length], expectedOutput[:length]) { + t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff[:length], expectedOutput[:length]) + } + } + }) + + t.Run("OutOfBoundsWrite", func(t *testing.T) { // Issue 21104 + rng := newRandReader(t) + + plaintext := make([]byte, bufCap) + rng.Read(plaintext) + ciphertext := make([]byte, bufCap) + + for _, length := range bufLens { + copy(ciphertext, plaintext) // Reset ciphertext buffer + + t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) { + mustPanic(t, "output smaller than input", func() { ms().XORKeyStream(ciphertext[:length], plaintext) }) + + if !bytes.Equal(ciphertext[length:], plaintext[length:]) { + t.Errorf("XORKeyStream did out of bounds write; got %s, want %s", truncateHex(ciphertext[length:]), truncateHex(plaintext[length:])) + } + }) + } + }) + + t.Run("BufferOverlap", func(t *testing.T) { + rng := newRandReader(t) + + buff := make([]byte, bufCap) + rng.Read(buff) + + for _, length := range bufLens { + if length == 0 || length == 1 { + continue + } + + t.Run(fmt.Sprintf("BuffLength=%d", length), func(t *testing.T) { + // Make src and dst slices point to same array with inexact overlap + src := buff[:length] + dst := buff[1 : length+1] + mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) }) + + // Only overlap on one byte + src = buff[:length] + dst = buff[length-1 : 2*length-1] + mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) }) + + // src comes after dst with one byte overlap + src = buff[length-1 : 2*length-1] + dst = buff[:length] + mustPanic(t, "invalid buffer overlap", func() { ms().XORKeyStream(dst, src) }) + }) + } + }) + + t.Run("KeepState", func(t *testing.T) { + rng := newRandReader(t) + + plaintext := make([]byte, bufCap) + rng.Read(plaintext) + ciphertext := make([]byte, bufCap) + + // Make one long call to XORKeyStream + ms().XORKeyStream(ciphertext, plaintext) + + for _, step := range bufLens { + if step == 0 { + continue + } + stepMsg := fmt.Sprintf("step %d: ", step) + + dst := make([]byte, bufCap) + + // Make a bunch of small calls to (stateful) XORKeyStream + stream := ms() + i := 0 + for i+step < len(plaintext) { + stream.XORKeyStream(dst[i:], plaintext[i:i+step]) + i += step + } + stream.XORKeyStream(dst[i:], plaintext[i:]) + + if !bytes.Equal(dst, ciphertext) { + t.Errorf(stepMsg+"successive XORKeyStream calls returned a different result than a single one; got %s, want %s", truncateHex(dst), truncateHex(ciphertext)) + } + } + }) +} + +// TestStreamFromBlock creates a Stream from a cipher.Block used in a +// cipher.BlockMode. It addresses Issue 68377 by checking for a panic when the +// BlockMode uses an IV with incorrect length. +// For a valid IV, it also runs all TestStream tests on the resulting stream. +func TestStreamFromBlock(t *testing.T, block cipher.Block, blockMode func(b cipher.Block, iv []byte) cipher.Stream) { + + t.Run("WrongIVLen", func(t *testing.T) { + t.Skip("see Issue 68377") + + rng := newRandReader(t) + iv := make([]byte, block.BlockSize()+1) + rng.Read(iv) + mustPanic(t, "IV length must equal block size", func() { blockMode(block, iv) }) + }) + + t.Run("BlockModeStream", func(t *testing.T) { + rng := newRandReader(t) + iv := make([]byte, block.BlockSize()) + rng.Read(iv) + + TestStream(t, func() cipher.Stream { return blockMode(block, iv) }) + }) +} + +func truncateHex(b []byte) string { + numVals := 50 + + if len(b) <= numVals { + return fmt.Sprintf("%x", b) + } + return fmt.Sprintf("%x...", b[:numVals]) +} diff --git a/zuc/eea.go b/zuc/eea.go index 6442dd7..ee189df 100644 --- a/zuc/eea.go +++ b/zuc/eea.go @@ -10,9 +10,21 @@ import ( const RoundWords = 32 +type eea struct { + zucState32 + x [4]byte // remaining bytes buffer + xLen int // number of remaining bytes +} + // NewCipher create a stream cipher based on key and iv aguments. func NewCipher(key, iv []byte) (cipher.Stream, error) { - return newZUCState(key, iv) + s, err := newZUCState(key, iv) + if err != nil { + return nil, err + } + c := new(eea) + c.zucState32 = *s + return c, nil } // NewEEACipher create a stream cipher based on key, count, bearer and direction arguments according specification. @@ -22,7 +34,13 @@ func NewEEACipher(key []byte, count, bearer, direction uint32) (cipher.Stream, e copy(iv[8:12], iv[:4]) iv[4] = byte(((bearer << 1) | (direction & 1)) << 2) iv[12] = iv[4] - return newZUCState(key, iv) + s, err := newZUCState(key, iv) + if err != nil { + return nil, err + } + c := new(eea) + c.zucState32 = *s + return c, nil } func genKeyStreamRev32Generic(keyStream []byte, pState *zucState32) { @@ -33,24 +51,41 @@ func genKeyStreamRev32Generic(keyStream []byte, pState *zucState32) { } } -func (c *zucState32) XORKeyStream(dst, src []byte) { +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") } + 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]) + return + } + } words := (len(src) + 3) / 4 rounds := words / RoundWords var keyBytes [RoundWords * 4]byte for i := 0; i < rounds; i++ { - genKeyStreamRev32(keyBytes[:], c) + genKeyStreamRev32(keyBytes[:], &c.zucState32) subtle.XORBytes(dst, src, keyBytes[:]) dst = dst[RoundWords*4:] src = src[RoundWords*4:] } if rounds*RoundWords < words { - genKeyStreamRev32(keyBytes[:4*(words-rounds*RoundWords)], c) - subtle.XORBytes(dst, src, keyBytes[:]) + byteLen := 4 * (words - rounds*RoundWords) + 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]) + } } } diff --git a/zuc/eea_test.go b/zuc/eea_test.go index b99ede9..514abde 100644 --- a/zuc/eea_test.go +++ b/zuc/eea_test.go @@ -1,8 +1,11 @@ package zuc import ( + "crypto/cipher" "encoding/hex" "testing" + + "github.com/emmansun/gmsm/internal/cryptotest" ) var zucEEATests = []struct { @@ -62,6 +65,14 @@ func Test_EEA(t *testing.T) { } } +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 benchmarkStream(b *testing.B, buf []byte) { b.SetBytes(int64(len(buf))) diff --git a/zuc/eia256_test.go b/zuc/eia256_test.go index b6327e1..e90dbb1 100644 --- a/zuc/eia256_test.go +++ b/zuc/eia256_test.go @@ -2,7 +2,10 @@ package zuc import ( "encoding/hex" + "hash" "testing" + + "github.com/emmansun/gmsm/internal/cryptotest" ) var zucEIA256Tests = []struct { @@ -260,6 +263,27 @@ func TestEIA256_Finish(t *testing.T) { } } +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 diff --git a/zuc/eia_test.go b/zuc/eia_test.go index db0db65..7650831 100644 --- a/zuc/eia_test.go +++ b/zuc/eia_test.go @@ -3,7 +3,10 @@ package zuc import ( "encoding/binary" "encoding/hex" + "hash" "testing" + + "github.com/emmansun/gmsm/internal/cryptotest" ) var key [16]byte @@ -204,3 +207,12 @@ func TestEIA_Sum(t *testing.T) { 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 + }) + }) +}