cipher: add SM legacy operation modes

This commit is contained in:
Sun Yimin 2023-12-06 14:45:08 +08:00 committed by GitHub
parent 67c80c82b4
commit ee397cdbd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 473 additions and 0 deletions

150
cipher/bc.go Normal file
View File

@ -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)
}

74
cipher/bc_test.go Normal file
View File

@ -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)
}
}
}

View File

@ -11,6 +11,40 @@ import (
"github.com/emmansun/gmsm/sm4" "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) { func BenchmarkSM4HCTREncrypt1K(b *testing.B) {
var key [16]byte var key [16]byte
var tweak [32]byte var tweak [32]byte

144
cipher/ofbnlf.go Normal file
View File

@ -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)
}

71
cipher/ofbnlf_test.go Normal file
View File

@ -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)
}
}
}