refactor: split into subpackages and add AEAD/options/stream APIs with comprehensive tests

This commit is contained in:
2026-03-14 15:39:21 +08:00
parent e722b958a4
commit e89350b56a
54 changed files with 6040 additions and 1961 deletions
+25
View File
@@ -0,0 +1,25 @@
package paddingx
import "testing"
func FuzzPadUnpadRoundTrip(f *testing.F) {
f.Add([]byte("abc"))
f.Add([]byte{})
modes := []string{PKCS7, ZERO, ANSIX923}
f.Fuzz(func(t *testing.T, data []byte) {
for _, mode := range modes {
padded, err := Pad(data, 16, mode)
if err != nil {
t.Fatalf("Pad failed: %v", err)
}
out, err := Unpad(padded, 16, mode)
if err != nil {
t.Fatalf("Unpad failed: %v", err)
}
if mode != ZERO && string(out) != string(data) {
t.Fatalf("roundtrip mismatch for mode %s", mode)
}
}
})
}
+128
View File
@@ -0,0 +1,128 @@
package paddingx
import (
"bytes"
"errors"
"strings"
)
const (
PKCS5 = "PKCS5"
PKCS7 = "PKCS7"
ZERO = "ZERO"
ANSIX923 = "ANSIX923"
)
func Pad(data []byte, blockSize int, mode string) ([]byte, error) {
if blockSize <= 0 {
return nil, errors.New("block size must be greater than zero")
}
switch normalizeMode(mode) {
case "", PKCS7:
return PKCS7Padding(data, blockSize), nil
case PKCS5:
// Compatibility mode: historically PKCS5 was used generically in this project.
return PKCS7Padding(data, blockSize), nil
case ZERO:
return zeroPadding(data, blockSize), nil
case ANSIX923:
return ansiX923Padding(data, blockSize), nil
default:
return nil, errors.New("padding type not supported")
}
}
func Unpad(data []byte, blockSize int, mode string) ([]byte, error) {
if blockSize <= 0 {
return nil, errors.New("block size must be greater than zero")
}
switch normalizeMode(mode) {
case "", PKCS7:
return PKCS7Unpadding(data, blockSize)
case PKCS5:
// Compatibility mode: historically PKCS5 was used generically in this project.
return PKCS7Unpadding(data, blockSize)
case ZERO:
return zeroUnpadding(data)
case ANSIX923:
return ansiX923Unpadding(data, blockSize)
default:
return nil, errors.New("padding type not supported")
}
}
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func PKCS7Unpadding(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, errors.New("invalid PKCS7 padding")
}
padding := int(data[len(data)-1])
if padding <= 0 || padding > blockSize || padding > len(data) {
return nil, errors.New("invalid PKCS7 padding")
}
for i := len(data) - padding; i < len(data); i++ {
if int(data[i]) != padding {
return nil, errors.New("invalid PKCS7 padding")
}
}
return data[:len(data)-padding], nil
}
func PKCS5Padding(data []byte) []byte {
return PKCS7Padding(data, 8)
}
func PKCS5Unpadding(data []byte) ([]byte, error) {
return PKCS7Unpadding(data, 8)
}
func zeroPadding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
if padding == blockSize {
return data
}
return append(data, bytes.Repeat([]byte{0x00}, padding)...)
}
func zeroUnpadding(data []byte) ([]byte, error) {
idx := len(data)
for idx > 0 && data[idx-1] == 0x00 {
idx--
}
return data[:idx], nil
}
func ansiX923Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
if padding == 0 {
padding = blockSize
}
pad := make([]byte, padding)
pad[len(pad)-1] = byte(padding)
return append(data, pad...)
}
func ansiX923Unpadding(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, errors.New("invalid ANSI X9.23 padding")
}
padding := int(data[len(data)-1])
if padding <= 0 || padding > blockSize || padding > len(data) {
return nil, errors.New("invalid ANSI X9.23 padding")
}
for i := len(data) - padding; i < len(data)-1; i++ {
if data[i] != 0x00 {
return nil, errors.New("invalid ANSI X9.23 padding")
}
}
return data[:len(data)-padding], nil
}
func normalizeMode(mode string) string {
return strings.ToUpper(strings.TrimSpace(mode))
}
+94
View File
@@ -0,0 +1,94 @@
package paddingx
import (
"bytes"
"testing"
)
func TestPadAndUnpadPKCS7(t *testing.T) {
plain := []byte("hello-world")
padded, err := Pad(plain, 16, PKCS7)
if err != nil {
t.Fatalf("Pad PKCS7 failed: %v", err)
}
if len(padded)%16 != 0 {
t.Fatalf("padded length should be block aligned, got %d", len(padded))
}
got, err := Unpad(padded, 16, PKCS7)
if err != nil {
t.Fatalf("Unpad PKCS7 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadAndUnpadPKCS5Compatibility(t *testing.T) {
plain := []byte("DES-plaintext")
padded, err := Pad(plain, 8, PKCS5)
if err != nil {
t.Fatalf("Pad PKCS5 failed: %v", err)
}
got, err := Unpad(padded, 8, PKCS5)
if err != nil {
t.Fatalf("Unpad PKCS5 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadAndUnpadZero(t *testing.T) {
plain := []byte("abc\x00\x00")
padded, err := Pad(plain, 8, ZERO)
if err != nil {
t.Fatalf("Pad ZERO failed: %v", err)
}
got, err := Unpad(padded, 8, ZERO)
if err != nil {
t.Fatalf("Unpad ZERO failed: %v", err)
}
if !bytes.Equal(got, []byte("abc")) {
t.Fatalf("zero unpadding mismatch, got %q", got)
}
}
func TestPadAndUnpadANSIX923(t *testing.T) {
plain := []byte("ansi-x923")
padded, err := Pad(plain, 16, ANSIX923)
if err != nil {
t.Fatalf("Pad ANSIX923 failed: %v", err)
}
got, err := Unpad(padded, 16, ANSIX923)
if err != nil {
t.Fatalf("Unpad ANSIX923 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadUnsupportedMode(t *testing.T) {
if _, err := Pad([]byte("x"), 8, "UNKNOWN"); err == nil {
t.Fatalf("expected error for unsupported mode")
}
}
func TestUnpadInvalidPKCS7(t *testing.T) {
_, err := Unpad([]byte{1, 2, 3, 4}, 4, PKCS7)
if err == nil {
t.Fatalf("expected invalid PKCS7 padding error")
}
}
func TestPKCS5Helpers(t *testing.T) {
plain := []byte("1234567")
padded := PKCS5Padding(plain)
got, err := PKCS5Unpadding(padded)
if err != nil {
t.Fatalf("PKCS5Unpadding failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("PKCS5 helper mismatch, got %x want %x", got, plain)
}
}