From ee397cdbd73048a87177a0fa6b269ab892e1c0cb Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Wed, 6 Dec 2023 14:45:08 +0800 Subject: [PATCH] cipher: add SM legacy operation modes --- cipher/bc.go | 150 +++++++++++++++++++++++++++++++++++++++ cipher/bc_test.go | 74 +++++++++++++++++++ cipher/benchmark_test.go | 34 +++++++++ cipher/ofbnlf.go | 144 +++++++++++++++++++++++++++++++++++++ cipher/ofbnlf_test.go | 71 ++++++++++++++++++ 5 files changed, 473 insertions(+) create mode 100644 cipher/bc.go create mode 100644 cipher/bc_test.go create mode 100644 cipher/ofbnlf.go create mode 100644 cipher/ofbnlf_test.go diff --git a/cipher/bc.go b/cipher/bc.go new file mode 100644 index 0000000..629d466 --- /dev/null +++ b/cipher/bc.go @@ -0,0 +1,150 @@ +// Block Chaining operation mode (BC mode) in Chinese national standard GB/T 17964-2021. +// See GB/T 17964-2021 Chapter 12. +package cipher + +import ( + _cipher "crypto/cipher" + + "github.com/emmansun/gmsm/internal/alias" + "github.com/emmansun/gmsm/internal/subtle" +) + +type bc struct { + b _cipher.Block + blockSize int + iv []byte +} + +func newBC(b _cipher.Block, iv []byte) *bc { + c := &bc{ + b: b, + blockSize: b.BlockSize(), + iv: make([]byte, b.BlockSize()), + } + copy(c.iv, iv) + return c +} + +type bcEncrypter bc + +// bcEncAble is an interface implemented by ciphers that have a specific +// optimized implementation of BC encryption. +// NewBCEncrypter will check for this interface and return the specific +// BlockMode if found. +type bcEncAble interface { + NewBCEncrypter(iv []byte) _cipher.BlockMode +} + +// NewBCEncrypter returns a BlockMode which encrypts in block chaining +// mode, using the given Block. The length of iv must be the same as the +// Block's block size. +func NewBCEncrypter(b _cipher.Block, iv []byte) _cipher.BlockMode { + if len(iv) != b.BlockSize() { + panic("cipher.NewBCEncrypter: IV length must equal block size") + } + if bc, ok := b.(bcEncAble); ok { + return bc.NewBCEncrypter(iv) + } + return (*bcEncrypter)(newBC(b, iv)) +} + +func (x *bcEncrypter) BlockSize() int { return x.blockSize } + +func (x *bcEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("cipher: invalid buffer overlap") + } + + iv := x.iv + + for len(src) > 0 { + // Write the xor to dst, then encrypt in place. + subtle.XORBytes(dst[:x.blockSize], src[:x.blockSize], iv) + x.b.Encrypt(dst[:x.blockSize], dst[:x.blockSize]) + subtle.XORBytes(iv, iv, dst[:x.blockSize]) + + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } + + // Save the iv for the next CryptBlocks call. + copy(x.iv, iv) +} + +func (x *bcEncrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv, iv) +} + +type bcDecrypter bc + +// bcDecAble is an interface implemented by ciphers that have a specific +// optimized implementation of BC decryption. +// NewBCDecrypter will check for this interface and return the specific +// BlockMode if found. +type bcDecAble interface { + NewBCDecrypter(iv []byte) _cipher.BlockMode +} + +// NewBCDecrypter returns a BlockMode which decrypts in block chaining +// mode, using the given Block. The length of iv must be the same as the +// Block's block size and must match the iv used to encrypt the data. +func NewBCDecrypter(b _cipher.Block, iv []byte) _cipher.BlockMode { + if len(iv) != b.BlockSize() { + panic("cipher.NewBCDecrypter: IV length must equal block size") + } + if bc, ok := b.(bcDecAble); ok { + return bc.NewBCDecrypter(iv) + } + return (*bcDecrypter)(newBC(b, iv)) +} + +func (x *bcDecrypter) BlockSize() int { return x.blockSize } + +func (x *bcDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("cipher: invalid buffer overlap") + } + if len(src) == 0 { + return + } + + iv := x.iv + nextIV := make([]byte, x.blockSize) + + for len(src) > 0 { + // Get F(i+1) + subtle.XORBytes(nextIV, iv, src[:x.blockSize]) + // Get plaintext P(i) + x.b.Decrypt(dst[:x.blockSize], src[:x.blockSize]) + subtle.XORBytes(dst[:x.blockSize], dst[:x.blockSize], iv) + + copy(iv, nextIV) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + } + + // Save the iv for the next CryptBlocks call. + copy(x.iv, iv) +} + +func (x *bcDecrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv, iv) +} diff --git a/cipher/bc_test.go b/cipher/bc_test.go new file mode 100644 index 0000000..f68ebb6 --- /dev/null +++ b/cipher/bc_test.go @@ -0,0 +1,74 @@ +package cipher_test + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "testing" + + "github.com/emmansun/gmsm/cipher" + "github.com/emmansun/gmsm/sm4" +) + +var bcSM4TestVectors = []struct { + key string + iv string + plaintext string + ciphertext string +}{ + { + "2B7E151628AED2A6ABF7158809CF4F3C", + "000102030405060708090A0B0C0D0E0F", + "6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710", + "AC529AF989A62FCE9CDDC5FFB84125CAFB8CDE77339FFE481D113C40BBD5B6786FFC9916F98F94FF12D78319707E240428718707605BC1EAC503153EBAA0FB1D", + }, +} + +func TestBC(t *testing.T) { + for i, test := range bcSM4TestVectors { + key, _ := hex.DecodeString(test.key) + iv, _ := hex.DecodeString(test.iv) + plaintext, _ := hex.DecodeString(test.plaintext) + ciphertext, _ := hex.DecodeString(test.ciphertext) + got := make([]byte, len(plaintext)) + c, err := sm4.NewCipher(key) + if err != nil { + t.Fatal(err) + } + + encrypter := cipher.NewBCEncrypter(c, iv) + encrypter.CryptBlocks(got, plaintext) + if !bytes.Equal(got, ciphertext) { + t.Fatalf("%v case encrypt failed, got %x\n", i+1, got) + } + + decrypter := cipher.NewBCDecrypter(c, iv) + decrypter.CryptBlocks(got, ciphertext) + if !bytes.Equal(got, plaintext) { + t.Fatalf("%v case decrypt failed, got %x\n", i+1, got) + } + } +} + +func TestSM4BCRandom(t *testing.T) { + key, _ := hex.DecodeString(bcSM4TestVectors[0].key) + iv := []byte("0123456789ABCDEF") + c, err := sm4.NewCipher(key) + if err != nil { + t.Fatal(err) + } + encrypter := cipher.NewBCEncrypter(c, iv) + decrypter := cipher.NewBCDecrypter(c, iv) + for i := 1; i <= 50; i++ { + plaintext := make([]byte, i*16) + ciphertext := make([]byte, i*16) + got := make([]byte, i*16) + io.ReadFull(rand.Reader, plaintext) + encrypter.CryptBlocks(ciphertext, plaintext) + decrypter.CryptBlocks(got, ciphertext) + if !bytes.Equal(got, plaintext) { + t.Errorf("test %v blocks failed", i) + } + } +} diff --git a/cipher/benchmark_test.go b/cipher/benchmark_test.go index 085008b..83d6d39 100644 --- a/cipher/benchmark_test.go +++ b/cipher/benchmark_test.go @@ -11,6 +11,40 @@ import ( "github.com/emmansun/gmsm/sm4" ) +func BenchmarkSM4BCEncrypt1K(b *testing.B) { + var key [16]byte + c, _ := sm4.NewCipher(key[:]) + benchmarkBCEncrypt1K(b, c) +} + +func benchmarkBCEncrypt1K(b *testing.B, block cipher.Block) { + buf := make([]byte, 1024) + b.SetBytes(int64(len(buf))) + + var iv [16]byte + bc := smcipher.NewBCEncrypter(block, iv[:]) + for i := 0; i < b.N; i++ { + bc.CryptBlocks(buf, buf) + } +} + +func BenchmarkSM4BCDecrypt1K(b *testing.B) { + var key [16]byte + c, _ := sm4.NewCipher(key[:]) + benchmarkBCDecrypt1K(b, c) +} + +func benchmarkBCDecrypt1K(b *testing.B, block cipher.Block) { + buf := make([]byte, 1024) + b.SetBytes(int64(len(buf))) + + var iv [16]byte + bc := smcipher.NewBCDecrypter(block, iv[:]) + for i := 0; i < b.N; i++ { + bc.CryptBlocks(buf, buf) + } +} + func BenchmarkSM4HCTREncrypt1K(b *testing.B) { var key [16]byte var tweak [32]byte diff --git a/cipher/ofbnlf.go b/cipher/ofbnlf.go new file mode 100644 index 0000000..9011be6 --- /dev/null +++ b/cipher/ofbnlf.go @@ -0,0 +1,144 @@ +// Output feedback with a nonlinear function operation mode (OFBNLF mode) in Chinese national standard GB/T 17964-2021. +// See GB/T 17964-2021 Chapter 13. + +package cipher + +import ( + _cipher "crypto/cipher" + "errors" + + "github.com/emmansun/gmsm/internal/alias" +) + +type ofbnlf struct { + cipherFunc CipherCreator + b _cipher.Block + blockSize int + iv []byte +} + +func newOFBNLF(cipherFunc CipherCreator, key, iv []byte) (*ofbnlf, error) { + c := &ofbnlf{ + cipherFunc: cipherFunc, + } + var err error + c.b, err = cipherFunc(key) + if err != nil { + return nil, err + } + c.blockSize = c.b.BlockSize() + if len(iv) != c.blockSize { + return nil, errors.New("cipher: IV length must equal block size") + } + c.iv = make([]byte, c.blockSize) + copy(c.iv, iv) + return c, nil +} + +type ofbnlfEncrypter ofbnlf + +// NewOFBNLFEncrypter returns a BlockMode which encrypts in Output feedback +// with a nonlinear function operation mode, using the given Block. +// The length of iv must be the same as the Block's block size. +func NewOFBNLFEncrypter(cipherFunc CipherCreator, key, iv []byte) (_cipher.BlockMode, error) { + c, err := newOFBNLF(cipherFunc, key, iv) + if err != nil { + return nil, err + } + return (*ofbnlfEncrypter)(c), nil +} + +func (x *ofbnlfEncrypter) BlockSize() int { return x.blockSize } + +func (x *ofbnlfEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("cipher: invalid buffer overlap") + } + + iv := x.iv + k := make([]byte, x.blockSize) + + for len(src) > 0 { + x.b.Encrypt(k, iv) + c, err := x.cipherFunc(k) + if err != nil { + panic(err) + } + c.Encrypt(dst, src) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + copy(iv, k) + } + + // Save the iv for the next CryptBlocks call. + copy(x.iv, iv) +} + +func (x *ofbnlfEncrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv, iv) +} + +type ofbnlfDecrypter ofbnlf + +// NewOFBNLFDecrypter returns a BlockMode which decrypts in Output feedback +// with a nonlinear function operation mode, using the given Block. +// The length of iv must be the same as the Block's block size and must match +// the iv used to encrypt the data. +func NewOFBNLFDecrypter(cipherFunc CipherCreator, key, iv []byte) (_cipher.BlockMode, error) { + c, err := newOFBNLF(cipherFunc, key, iv) + if err != nil { + return nil, err + } + return (*ofbnlfDecrypter)(c), nil +} + +func (x *ofbnlfDecrypter) BlockSize() int { return x.blockSize } + +func (x *ofbnlfDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%x.blockSize != 0 { + panic("cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("cipher: invalid buffer overlap") + } + if len(src) == 0 { + return + } + + iv := x.iv + k := make([]byte, x.blockSize) + + for len(src) > 0 { + x.b.Encrypt(k, iv) + c, err := x.cipherFunc(k) + if err != nil { + panic(err) + } + c.Decrypt(dst, src) + src = src[x.blockSize:] + dst = dst[x.blockSize:] + copy(iv, k) + } + + // Save the iv for the next CryptBlocks call. + copy(x.iv, iv) +} + +func (x *ofbnlfDecrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv, iv) +} diff --git a/cipher/ofbnlf_test.go b/cipher/ofbnlf_test.go new file mode 100644 index 0000000..6df96ea --- /dev/null +++ b/cipher/ofbnlf_test.go @@ -0,0 +1,71 @@ +package cipher_test + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "testing" + + "github.com/emmansun/gmsm/cipher" + "github.com/emmansun/gmsm/sm4" +) + +var ofbnlfSM4TestVectors = []struct { + key string + iv string + plaintext string + ciphertext string +}{ + { + "2B7E151628AED2A6ABF7158809CF4F3C", + "000102030405060708090A0B0C0D0E0F", + "6BC1BEE22E409F96E93D7E117393172AAE2D8A571E03AC9C9EB76FAC45AF8E5130C81C46A35CE411E5FBC1191A0A52EFF69F2445DF4F9B17AD2B417BE66C3710", + "00A5B5C9E645557C20CE7F267736F308A18037828850B9D78883CA622851F86CB7CAEFDFB6D4CABA6AE2D2FCE369CEB31001DD71FDDA9341F8D221CB720FF27B", + }, +} + +func TestOFBNLF(t *testing.T) { + for i, test := range ofbnlfSM4TestVectors { + key, _ := hex.DecodeString(test.key) + iv, _ := hex.DecodeString(test.iv) + plaintext, _ := hex.DecodeString(test.plaintext) + ciphertext, _ := hex.DecodeString(test.ciphertext) + got := make([]byte, len(plaintext)) + encrypter, err := cipher.NewOFBNLFEncrypter(sm4.NewCipher, key, iv) + if err != nil { + t.Fatal(err) + } + encrypter.CryptBlocks(got, plaintext) + if !bytes.Equal(got, ciphertext) { + t.Fatalf("%v case encrypt failed, got %x\n", i+1, got) + } + + decrypter, err := cipher.NewOFBNLFDecrypter(sm4.NewCipher, key, iv) + if err != nil { + t.Fatal(err) + } + decrypter.CryptBlocks(got, ciphertext) + if !bytes.Equal(got, plaintext) { + t.Fatalf("%v case decrypt failed, got %x\n", i+1, got) + } + } +} + +func TestSM4OFBNLFRandom(t *testing.T) { + key, _ := hex.DecodeString(ofbnlfSM4TestVectors[0].key) + iv := []byte("0123456789ABCDEF") + encrypter, _ := cipher.NewOFBNLFEncrypter(sm4.NewCipher, key, iv) + decrypter, _ := cipher.NewOFBNLFDecrypter(sm4.NewCipher, key, iv) + for i := 1; i <= 50; i++ { + plaintext := make([]byte, i*16) + ciphertext := make([]byte, i*16) + got := make([]byte, i*16) + io.ReadFull(rand.Reader, plaintext) + encrypter.CryptBlocks(ciphertext, plaintext) + decrypter.CryptBlocks(got, ciphertext) + if !bytes.Equal(got, plaintext) { + t.Errorf("test %v blocks failed", i) + } + } +}