From d389da6bcb0a0fa57cd20527be7843c87c40c3f1 Mon Sep 17 00:00:00 2001 From: Emman Date: Thu, 24 Feb 2022 17:41:52 +0800 Subject: [PATCH] Extract padding utility --- padding/pads.go | 15 +++++++ padding/pkcs7.go | 44 ++++++++++++++++++++ padding/pkcs7_test.go | 89 ++++++++++++++++++++++++++++++++++++++++ sm4_test/cbc_sm4_test.go | 33 +++------------ 4 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 padding/pads.go create mode 100644 padding/pkcs7.go create mode 100644 padding/pkcs7_test.go diff --git a/padding/pads.go b/padding/pads.go new file mode 100644 index 0000000..7728e34 --- /dev/null +++ b/padding/pads.go @@ -0,0 +1,15 @@ +package padding + +// Padding interface represents a padding scheme +type Padding interface { + BlockSize() int + Pad(src []byte) []byte + Unpad(src []byte) ([]byte, error) +} + +func NewPKCS7Padding(blockSize uint) Padding { + if blockSize == 0 || blockSize > 255 { + panic("padding: invalid block size") + } + return pkcs7Padding(blockSize) +} diff --git a/padding/pkcs7.go b/padding/pkcs7.go new file mode 100644 index 0000000..c070519 --- /dev/null +++ b/padding/pkcs7.go @@ -0,0 +1,44 @@ +// https://datatracker.ietf.org/doc/html/rfc5652#section-6.3 +package padding + +import ( + goSubtle "crypto/subtle" + "errors" + + "github.com/emmansun/gmsm/internal/subtle" +) + +type pkcs7Padding uint + +func (pad pkcs7Padding) BlockSize() int { + return int(pad) +} + +func (pad pkcs7Padding) Pad(src []byte) []byte { + overhead := pad.BlockSize() - len(src)%pad.BlockSize() + ret, out := subtle.SliceForAppend(src, overhead) + for i := 0; i < overhead; i++ { + out[i] = byte(overhead) + } + return ret +} + +func (pad pkcs7Padding) Unpad(src []byte) ([]byte, error) { + if len(src)%pad.BlockSize() != 0 { + return nil, errors.New("pkcs7: invalid src size") + } + overhead := src[len(src)-1] + if overhead == 0 || int(overhead) > pad.BlockSize() { + return nil, errors.New("pkcs7: invalid padding byte/length") + } + tag := make([]byte, pad.BlockSize()) + copy(tag, src[len(src)-pad.BlockSize():]) + for i := pad.BlockSize() - int(overhead); i < pad.BlockSize(); i++ { + tag[i] = byte(overhead) + } + if goSubtle.ConstantTimeCompare(tag, src[len(src)-pad.BlockSize():]) != 1 { + return nil, errors.New("pkcs7: inconsistent padding bytes") + } + + return src[:len(src)-int(overhead)], nil +} diff --git a/padding/pkcs7_test.go b/padding/pkcs7_test.go new file mode 100644 index 0000000..0d359fd --- /dev/null +++ b/padding/pkcs7_test.go @@ -0,0 +1,89 @@ +// https://datatracker.ietf.org/doc/html/rfc5652#section-6.3 + +package padding + +import ( + "reflect" + "testing" +) + +func Test_pkcs7Padding_Pad(t *testing.T) { + pad := NewPKCS7Padding(16) + + tests := []struct { + name string + src []byte + want []byte + }{ + // TODO: Add test cases. + {"16 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}}, + {"15 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1}}, + {"14 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 2, 2}}, + {"13 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 3, 3}}, + {"12 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 4, 4, 4, 4}}, + {"11 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 5, 5, 5, 5, 5}}, + {"10 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 6, 6, 6, 6, 6}}, + {"9 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 7, 7, 7, 7, 7, 7}}, + {"8 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8}}, + {"7 bytes", []byte{0, 1, 2, 3, 4, 5, 6}, []byte{0, 1, 2, 3, 4, 5, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9}}, + {"6 bytes", []byte{0, 1, 2, 3, 4, 5}, []byte{0, 1, 2, 3, 4, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}}, + {"5 bytes", []byte{0, 1, 2, 3, 4}, []byte{0, 1, 2, 3, 4, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}}, + {"4 bytes", []byte{0, 1, 2, 3}, []byte{0, 1, 2, 3, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}}, + {"3 bytes", []byte{0, 1, 2}, []byte{0, 1, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}}, + {"2 bytes", []byte{0, 1}, []byte{0, 1, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14}}, + {"1 bytes", []byte{0}, []byte{0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pad.Pad(tt.src); !reflect.DeepEqual(got, tt.want) { + t.Errorf("pkcs7Padding.Pad() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_pkcs7Padding_Unpad(t *testing.T) { + pad := NewPKCS7Padding(16) + type args struct { + src []byte + } + tests := []struct { + name string + want []byte + src []byte + wantErr bool + }{ + // TODO: Add test cases. + {"16 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}, false}, + {"15 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1}, false}, + {"14 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 2, 2}, false}, + {"13 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 3, 3, 3}, false}, + {"12 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 4, 4, 4, 4}, false}, + {"11 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 5, 5, 5, 5, 5}, false}, + {"10 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 6, 6, 6, 6, 6}, false}, + {"9 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 7, 7, 7, 7, 7, 7}, false}, + {"8 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8}, false}, + {"7 bytes", []byte{0, 1, 2, 3, 4, 5, 6}, []byte{0, 1, 2, 3, 4, 5, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9}, false}, + {"6 bytes", []byte{0, 1, 2, 3, 4, 5}, []byte{0, 1, 2, 3, 4, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, false}, + {"5 bytes", []byte{0, 1, 2, 3, 4}, []byte{0, 1, 2, 3, 4, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}, false}, + {"4 bytes", []byte{0, 1, 2, 3}, []byte{0, 1, 2, 3, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}, false}, + {"3 bytes", []byte{0, 1, 2}, []byte{0, 1, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}, false}, + {"2 bytes", []byte{0, 1}, []byte{0, 1, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14}, false}, + {"1 bytes", []byte{0}, []byte{0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}, false}, + {"invalid src length", nil, []byte{0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}, true}, + {"invalid padding byte", nil, []byte{0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17}, true}, + {"inconsistent padding bytes", nil, []byte{0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := pad.Unpad(tt.src) + if (err != nil) != tt.wantErr { + t.Errorf("pkcs7Padding.Unpad() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("pkcs7Padding.Unpad() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/sm4_test/cbc_sm4_test.go b/sm4_test/cbc_sm4_test.go index 6555b81..bba1655 100644 --- a/sm4_test/cbc_sm4_test.go +++ b/sm4_test/cbc_sm4_test.go @@ -4,38 +4,13 @@ import ( "bytes" "crypto/cipher" "encoding/hex" - "errors" "fmt" "testing" + "github.com/emmansun/gmsm/padding" "github.com/emmansun/gmsm/sm4" ) -func paddingPKCS7(buf []byte, blockSize int) []byte { - bufLen := len(buf) - padLen := blockSize - bufLen%blockSize - padded := make([]byte, bufLen+padLen) - copy(padded, buf) - for i := 0; i < padLen; i++ { - padded[bufLen+i] = byte(padLen) - } - return padded -} - -func unpaddingPKCS7(padded []byte, size int) ([]byte, error) { - if len(padded)%size != 0 { - return nil, errors.New("pkcs7: Padded value wasn't in correct size") - } - paddedByte := int(padded[len(padded)-1]) - if (paddedByte > size) || (paddedByte < 1) { - return nil, fmt.Errorf("Invalid decrypted text, no padding") - } - bufLen := len(padded) - paddedByte - buf := make([]byte, bufLen) - copy(buf, padded[:bufLen]) - return buf, nil -} - var cbcSM4Tests = []struct { name string key []byte @@ -143,6 +118,7 @@ var cbcSM4Tests = []struct { } func TestCBCEncrypterSM4(t *testing.T) { + pad := padding.NewPKCS7Padding(sm4.BlockSize) for _, test := range cbcSM4Tests { c, err := sm4.NewCipher(test.key) if err != nil { @@ -152,7 +128,7 @@ func TestCBCEncrypterSM4(t *testing.T) { encrypter := cipher.NewCBCEncrypter(c, test.iv) - plainText := paddingPKCS7(test.in, sm4.BlockSize) + plainText := pad.Pad(test.in) data := make([]byte, len(plainText)) copy(data, plainText) @@ -170,6 +146,7 @@ func TestCBCEncrypterSM4(t *testing.T) { } func TestCBCDecrypterSM4(t *testing.T) { + pad := padding.NewPKCS7Padding(sm4.BlockSize) for _, test := range cbcSM4Tests { c, err := sm4.NewCipher(test.key) if err != nil { @@ -183,7 +160,7 @@ func TestCBCDecrypterSM4(t *testing.T) { copy(data, test.out) decrypter.CryptBlocks(data, data) - data, err = unpaddingPKCS7(data, sm4.BlockSize) + data, err = pad.Unpad(data) if err != nil { t.Fatal(err) }