diff --git a/padding/iso9797_m2.go b/padding/iso9797_m2.go index cfec485..415f752 100644 --- a/padding/iso9797_m2.go +++ b/padding/iso9797_m2.go @@ -6,7 +6,7 @@ import ( "github.com/emmansun/gmsm/internal/alias" ) -// Add a single bit with value 1 to the end of the data. +// Add a single bit with value 1 to the end of the data. // Then if necessary add bits with value 0 to the end of the data until the padded data is a multiple of n. // // https://en.wikipedia.org/wiki/ISO/IEC_9797-1 @@ -31,17 +31,21 @@ func (pad iso9797M2Padding) Unpad(src []byte) ([]byte, error) { if srcLen == 0 || srcLen%pad.BlockSize() != 0 { return nil, errors.New("padding: src length is not multiple of block size") } - padStart := -1 - - for i, b := range src[srcLen-pad.BlockSize() : srcLen] { - if b == 0x80 { + tail := src[srcLen-pad.BlockSize():] + allZero := true + padStart := 0 + for i := pad.BlockSize() - 1; i >= 0; i-- { + if tail[i] == 0x80 { padStart = i - } else if b != 0 && padStart >=0 { - padStart = -1 + break + } + if tail[i] != 0 { + allZero = false + break } } - if padStart >= 0 { - return src[:srcLen-pad.BlockSize()+padStart], nil + if !allZero { + return nil, errors.New("padding: inconsistent padding bytes") } - return src, nil + return src[:srcLen-pad.BlockSize()+padStart], nil } diff --git a/padding/iso9797_m2_test.go b/padding/iso9797_m2_test.go index 0fd8977..9b8252f 100644 --- a/padding/iso9797_m2_test.go +++ b/padding/iso9797_m2_test.go @@ -28,6 +28,7 @@ func Test_iso9797M2Padding_Pad(t *testing.T) { {"3 bytes", []byte{0, 1, 2}, []byte{0, 1, 2, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {"2 bytes", []byte{0, 1}, []byte{0, 1, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {"1 bytes", []byte{0}, []byte{0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"0 bytes", []byte{}, []byte{0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -62,10 +63,12 @@ func Test_iso9797M2Padding_Unpad(t *testing.T) { {"3 bytes", []byte{0, 1, 2}, []byte{0, 1, 2, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"2 bytes", []byte{0, 1}, []byte{0, 1, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"1 bytes", []byte{0}, []byte{0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"0 bytes", []byte{}, []byte{0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"1 bytes with tag", []byte{0x80}, []byte{0x80, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"3 bytes with tag", []byte{0x80, 0, 0x80}, []byte{0x80, 0, 0x80, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"19 bytes with tag", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x80, 0, 0x80}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x80, 0, 0x80, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, {"invalid src length", nil, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {"inconsistent padding bytes", nil, []byte{0, 0x80, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/padding/iso9797_m3.go b/padding/iso9797_m3.go new file mode 100644 index 0000000..ce15b55 --- /dev/null +++ b/padding/iso9797_m3.go @@ -0,0 +1,71 @@ +package padding + +import ( + "errors" + + "github.com/emmansun/gmsm/internal/byteorder" +) + +// The padded data comprises (in this order): +// +// - The length of the unpadded data (in bits) expressed in big-endian binary in n bits (i.e. one cipher block) +// - The unpadded data +// - As many (possibly none) bits with value 0 as are required to bring the total length to a multiple of n bits +// It is not necessary to transmit or store the padding bits, because the recipient can regenerate them, knowing the length of the unpadded data and the padding method used. +// +// https://en.wikipedia.org/wiki/ISO/IEC_9797-1#Padding_method_3 +// also GB/T 17964-2021 C.4 Padding method 3 +type iso9797M3Padding uint + +func (pad iso9797M3Padding) BlockSize() int { + return int(pad) +} + +func (pad iso9797M3Padding) Pad(src []byte) []byte { + srcLen := len(src) + overhead := pad.BlockSize() - srcLen%pad.BlockSize() + if overhead == pad.BlockSize() && srcLen > 0 { + overhead = 0 + } + + var head, tail []byte + total := srcLen + overhead + pad.BlockSize() + if cap(src) >= total { + head = src[:total] + } else { + head = make([]byte, total) + } + + tail = head[srcLen+pad.BlockSize():] + clear(head[:pad.BlockSize()]) + copy(head[pad.BlockSize():], src) + if overhead > 0 { + clear(tail) + } + byteorder.BEPutUint64(head[8:], uint64(srcLen*8)) + return head +} + +// Unpad decrypted plaintext, non-constant-time +func (pad iso9797M3Padding) Unpad(src []byte) ([]byte, error) { + srcLen := len(src) + if srcLen < 2*pad.BlockSize() || srcLen%pad.BlockSize() != 0 { + return nil, errors.New("padding: invalid src length") + } + for _, b := range src[:8] { + if b != 0 { + return nil, errors.New("padding: invalid padding header") + } + } + dstLen := int(byteorder.BEUint64(src[8:pad.BlockSize()])/8) + if dstLen < 0 || dstLen > srcLen-pad.BlockSize() { + return nil, errors.New("padding: invalid padding header") + } + padded := src[pad.BlockSize()+dstLen:] + for _, b := range padded { + if b != 0 { + return nil, errors.New("padding: invalid padding bytes") + } + } + return src[pad.BlockSize() : pad.BlockSize()+dstLen], nil +} diff --git a/padding/iso9797_m3_test.go b/padding/iso9797_m3_test.go new file mode 100644 index 0000000..41c2de0 --- /dev/null +++ b/padding/iso9797_m3_test.go @@ -0,0 +1,84 @@ +package padding + +import ( + "reflect" + "testing" +) + +func Test_iso9797M3Padding_Pad(t *testing.T) { + iso9797 := NewISO9797M3Padding(16) + tests := []struct { + name string + src []byte + want []byte + }{ + {"16 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}, + {"15 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0}}, + {"14 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0}}, + {"13 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}}, + {"12 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0}}, + {"11 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0}}, + {"10 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0}}, + {"9 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0}}, + {"8 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"7 bytes", []byte{0, 1, 2, 3, 4, 5, 6}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"6 bytes", []byte{0, 1, 2, 3, 4, 5}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"5 bytes", []byte{0, 1, 2, 3, 4}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"4 bytes", []byte{0, 1, 2, 3}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"3 bytes", []byte{0, 1, 2}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"2 bytes", []byte{0, 1}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"1 bytes", []byte{1}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"0 bytes", []byte{}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := iso9797.Pad(tt.src); !reflect.DeepEqual(got, tt.want) { + t.Errorf("iso9797M2Padding.Pad() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_iso9797M3Padding_Unpad(t *testing.T) { + iso9797 := NewISO9797M3Padding(16) + tests := []struct { + name string + want []byte + src []byte + wantErr bool + }{ + {"16 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, false}, + {"15 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0}, false}, + {"14 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0}, false}, + {"13 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}, false}, + {"12 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0}, false}, + {"11 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0}, false}, + {"10 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0}, false}, + {"9 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0}, false}, + {"8 bytes", []byte{0, 1, 2, 3, 4, 5, 6, 7}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"7 bytes", []byte{0, 1, 2, 3, 4, 5, 6}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"6 bytes", []byte{0, 1, 2, 3, 4, 5}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"5 bytes", []byte{0, 1, 2, 3, 4}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"4 bytes", []byte{0, 1, 2, 3}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"3 bytes", []byte{0, 1, 2}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"2 bytes", []byte{0, 1}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"1 bytes", []byte{1}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"0 bytes", []byte{}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false}, + {"invalid length", nil, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {"invalid padding header", nil, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {"invalid padding header 2", nil, []byte{0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, + {"invalid padding bytes", nil, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := iso9797.Unpad(tt.src) + if (err != nil) != tt.wantErr { + t.Errorf("case %v: iso9797M2Padding.Unpad() error = %v, wantErr %v", tt.name, err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("case %v: iso9797M2Padding.Unpad() = %v, want %v", tt.name, got, tt.want) + } + }) + } +} diff --git a/padding/schemes.go b/padding/schemes.go index e1f6c06..c0c8681 100644 --- a/padding/schemes.go +++ b/padding/schemes.go @@ -28,3 +28,10 @@ func NewISO9797M2Padding(blockSize uint) Padding { } return iso9797M2Padding(blockSize) } + +func NewISO9797M3Padding(blockSize uint) Padding { + if blockSize == 0 || blockSize > 255 { + panic("padding: invalid block size") + } + return iso9797M3Padding(blockSize) +}