完善不同加密方法的使用

This commit is contained in:
yun
2026-09-30 15:49:12 +08:00
parent 034315ddbd
commit 7db722f8b9
69 changed files with 21362 additions and 2563 deletions
+345 -25
View File
@@ -1,43 +1,363 @@
package base64x_test
import (
"bytes"
"encoding/base64"
"strings"
"testing"
"code.yun.ink/pkg/encryptx/base64x"
)
func TestBase64xStd(t *testing.T) {
original := "Hello, World!"
encoded := base64x.StdEncode(original)
decoded, err := base64x.StdDecode(encoded)
if err != nil {
t.Error(err)
}
if original != decoded {
t.Errorf("expected %q, got %q", original, decoded)
// variants 汇总四个编解码变体,便于表驱动测试。
var variants = []struct {
name string
encode func([]byte) string
decode func(string) ([]byte, error)
// alphabetURL 表示该变体使用 URL 安全字母表(不含 + 与 /)。
alphabetURL bool
// padded 表示该变体输出带 = 填充。
padded bool
}{
{"标准", base64x.Encode, base64x.Decode, false, true},
{"URL安全", base64x.EncodeURL, base64x.DecodeURL, true, true},
{"标准无填充", base64x.EncodeRaw, base64x.DecodeRaw, false, false},
{"URL安全无填充", base64x.EncodeRawURL, base64x.DecodeRawURL, true, false},
}
// payloads 覆盖空、短、UTF-8 与二进制等各类输入。
var payloads = [][]byte{
nil,
{},
[]byte("a"),
[]byte("ab"),
[]byte("abc"),
[]byte("Hello, World!"),
[]byte("中文与 emoji 🚀 混合内容"),
bytes.Repeat([]byte{0x00}, 64),
bytes.Repeat([]byte{0xff, 0x00, 0x7f, 0x80}, 128),
func() []byte {
b := make([]byte, 256)
for i := range b {
b[i] = byte(i)
}
return b
}(),
}
// TestRoundTrip 校验四个变体各自的编解码往返一致。
func TestRoundTrip(t *testing.T) {
for _, v := range variants {
for i, payload := range payloads {
encoded := v.encode(payload)
decoded, err := v.decode(encoded)
if err != nil {
t.Fatalf("%s 用例 %d: 解码失败: %v", v.name, i, err)
}
if !bytes.Equal(decoded, payload) {
t.Errorf("%s 用例 %d: 往返不一致,得到 %x,期望 %x", v.name, i, decoded, payload)
}
}
}
}
func TestBase64xUrl(t *testing.T) {
original := "Hello, World!"
encoded := base64x.UrlEncode(original)
decoded, err := base64x.UrlDecode(encoded)
if err != nil {
t.Error(err)
// TestEncodingFormat 校验各变体的字符集与填充约定。
func TestEncodingFormat(t *testing.T) {
// 选用会使标准 Base64 产生 "+" 与 "/" 的字节,以验证 URL 安全变体确实替换了它们。
payload := []byte{0xfb, 0xff, 0xbf, 0x00, 0xfe, 0x3e, 0x3f}
if got := base64x.Encode(payload); !strings.ContainsAny(got, "+/") {
t.Fatalf("测试数据未能触发标准字母表的 +/ 字符,实际编码为 %q", got)
}
if original != decoded {
t.Errorf("expected %q, got %q", original, decoded)
for _, v := range variants {
encoded := v.encode(payload)
if v.alphabetURL {
if strings.ContainsAny(encoded, "+/") {
t.Errorf("%s 编码结果含非 URL 安全字符: %q", v.name, encoded)
}
}
hasPadding := strings.HasSuffix(encoded, "=")
if hasPadding != v.padded {
t.Errorf("%s 填充状态错误: 编码为 %q, 期望带填充=%v", v.name, encoded, v.padded)
}
if v.padded && len(encoded)%4 != 0 {
t.Errorf("%s 带填充编码长度应为 4 的倍数: %q", v.name, encoded)
}
if !isASCII(encoded) {
t.Errorf("%s 编码结果含非 ASCII 字符: %q", v.name, encoded)
}
}
}
func TestBase64xRaw(t *testing.T) {
original := "Hello, World!"
encoded := base64x.RawEncode(original)
decoded, err := base64x.RawDecode(encoded)
if err != nil {
t.Error(err)
// TestEncodeEdgeLengths 校验填充边界(1/2/3 字节分组)的编码结果符合 RFC 4648。
func TestEncodeEdgeLengths(t *testing.T) {
cases := []struct {
in []byte
std string
raw string
url string
rawU string
}{
{[]byte("f"), "Zg==", "Zg", "Zg==", "Zg"},
{[]byte("fo"), "Zm8=", "Zm8", "Zm8=", "Zm8"},
{[]byte("foo"), "Zm9v", "Zm9v", "Zm9v", "Zm9v"},
{[]byte("foob"), "Zm9vYg==", "Zm9vYg", "Zm9vYg==", "Zm9vYg"},
{[]byte("fooba"), "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE=", "Zm9vYmE"},
{[]byte("foobar"), "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"},
{[]byte{0xfb, 0xff, 0xbf}, "+/+/", "+/+/", "-_-_", "-_-_"},
}
if original != decoded {
t.Errorf("expected %q, got %q", original, decoded)
for _, c := range cases {
if got := base64x.Encode(c.in); got != c.std {
t.Errorf("Encode(%q) = %q, 期望 %q", c.in, got, c.std)
}
if got := base64x.EncodeRaw(c.in); got != c.raw {
t.Errorf("EncodeRaw(%q) = %q, 期望 %q", c.in, got, c.raw)
}
if got := base64x.EncodeURL(c.in); got != c.url {
t.Errorf("EncodeURL(%q) = %q, 期望 %q", c.in, got, c.url)
}
if got := base64x.EncodeRawURL(c.in); got != c.rawU {
t.Errorf("EncodeRawURL(%q) = %q, 期望 %q", c.in, got, c.rawU)
}
}
}
// TestDecodeRejectsGarbage 校验 Decode* 对垃圾输入返回 nil 切片与 error。
//
// 所有用例都已用实验确认对四个变体都真正非法(见 TestStdlibBehavior)。
func TestDecodeRejectsGarbage(t *testing.T) {
garbage := []struct {
name string
in string
}{
{"非字母表字符", "!!!not base64!!!"},
{"含空格", "SGVs bG8="},
{"含制表符", "SGVs\tbG8="},
{"只有填充", "===="},
{"单个填充", "="},
{"首字符即填充", "=AAAA"},
{"残留 1 个字符", "Z"},
{"错误填充位置", "SG=VsbG8="},
{"两段拼接的填充", "Zg==Zg=="},
{"填充数量过多", "A==="},
{"emoji 输入", "🚀🚀🚀🚀"},
{"中文输入", "你好世界"},
{"二进制控制字符", "\x00\x01\x02\x03"},
}
for _, v := range variants {
for _, g := range garbage {
decoded, err := v.decode(g.in)
if err == nil {
t.Errorf("%s 解码 %q(%s)应当失败,却成功得到 %x", v.name, g.in, g.name, decoded)
}
if decoded != nil {
t.Errorf("%s 解码 %q(%s)失败时应返回 nil,实际 %x", v.name, g.in, g.name, decoded)
}
if err != nil && err.Error() == "" {
t.Errorf("%s 解码 %q 返回了空错误信息", v.name, g.in)
}
}
}
}
// TestStdlibBehavior 固定住 Go encoding/base64 解码器的实际行为
// (以下断言全部由实验测得,不是推测)。这些行为会随封装一起暴露给调用方:
//
// 1. 解码器会**忽略** \r 与 \n(MIME 历史行为),但空格与制表符仍然非法;
// 2. 标准与 URL 两套字母表在解码时**不通用**:Std 只接受 '+'/'/',
// URL 只接受 '-'/'_'(编码方向同样区分);
// 3. 无填充解码器**拒绝**任何 '=' 填充,即使位置正确;
// 4. 带填充解码器接受"多余填充"与非规范尾部比特,
// 例如 "SGV=" 会被解码为 2 字节而不报错。
//
// 因此调用方不能依赖解码器拒绝所有畸形数据,需要自行约束输入格式。
func TestStdlibBehavior(t *testing.T) {
// 1. CR/LF 被忽略,空格与制表符非法。
if got, err := base64x.Decode("SGVsbG8=\n"); err != nil || !bytes.Equal(got, []byte("Hello")) {
t.Errorf("预期换行被忽略并解码出 Hello,得到 %x, err=%v", got, err)
}
if got, err := base64x.Decode("SGVsbG8=\r\n"); err != nil || !bytes.Equal(got, []byte("Hello")) {
t.Errorf("预期 CRLF 被忽略并解码出 Hello,得到 %x, err=%v", got, err)
}
if _, err := base64x.Decode("SGVs bG8="); err == nil {
t.Error("空格应导致解码失败")
}
if _, err := base64x.Decode("SGVs\tbG8="); err == nil {
t.Error("制表符应导致解码失败")
}
// 无填充解码器遇到换行同样失败(因为输入本身不完整/带填充)。
if _, err := base64x.DecodeRaw("SGVsbG8=\n"); err == nil {
t.Error("无填充解码器应拒绝含填充的输入")
}
// 2. 字母表不通用。
if got, err := base64x.Decode("a+b/"); err != nil || !bytes.Equal(got, []byte{0x6b, 0xe6, 0xff}) {
t.Errorf("标准解码器应接受 a+b/,得到 %x, err=%v", got, err)
}
if _, err := base64x.Decode("a-b_"); err == nil {
t.Error("标准解码器应拒绝 URL 字母表 a-b_")
}
if got, err := base64x.DecodeURL("a-b_"); err != nil || !bytes.Equal(got, []byte{0x6b, 0xe6, 0xff}) {
t.Errorf("URL 解码器应接受 a-b_,得到 %x, err=%v", got, err)
}
if _, err := base64x.DecodeURL("a+b/"); err == nil {
t.Error("URL 解码器应拒绝标准字母表 a+b/")
}
// 编码方向严格区分字母表。
if got := base64x.Encode([]byte{0xfb, 0xff, 0xbf}); got != "+/+/" {
t.Errorf("标准编码应为 +/+/,得到 %q", got)
}
if got := base64x.EncodeURL([]byte{0xfb, 0xff, 0xbf}); got != "-_-_" {
t.Errorf("URL 编码应为 -_-_,得到 %q", got)
}
// 3. 无填充解码器拒绝一切填充。
for _, in := range []string{"Zg==", "Zm8=", "SGVsbG8=", "AA==", "AAA="} {
if _, err := base64x.DecodeRaw(in); err == nil {
t.Errorf("无填充解码器应拒绝带填充输入 %q", in)
}
if _, err := base64x.DecodeRawURL(in); err == nil {
t.Errorf("无填充 URL 解码器应拒绝带填充输入 %q", in)
}
}
// 4. 带填充解码器接受多余填充与非规范尾部比特。
if got, err := base64x.Decode("SGV="); err != nil || !bytes.Equal(got, []byte("He")) {
t.Errorf("预期 SGV= 被宽松接受为 He(非规范尾部比特),得到 %x, err=%v", got, err)
}
// 而无填充解码器接受非规范尾部比特,但长度必须合法。
if got, err := base64x.DecodeRaw("SGV"); err != nil || !bytes.Equal(got, []byte("He")) {
t.Errorf("无填充解码器预期接受 SGV 为 He,得到 %x, err=%v", got, err)
}
if got, err := base64x.DecodeRaw("AA"); err != nil || !bytes.Equal(got, []byte{0x00}) {
t.Errorf("无填充解码器预期接受 AA 为 00,得到 %x, err=%v", got, err)
}
}
// TestPaddingSensitiveLengths 记录"长度与填充不匹配"时的实际行为:
// 带填充解码器要求输入长度为 4 的倍数,无填充解码器要求长度不为 4k+1。
func TestPaddingSensitiveLengths(t *testing.T) {
// 应当成功的用例(合法长度)。
ok := []struct {
name string
in string
dec func(string) ([]byte, error)
want []byte
}{
{"标准 4 字节分组", "AAAA", base64x.Decode, []byte{0x00, 0x00, 0x00}},
{"标准带填充 1 字节", "Zg==", base64x.Decode, []byte("f")},
{"标准带填充 2 字节", "Zm8=", base64x.Decode, []byte("fo")},
{"无填充 2 字符", "Zg", base64x.DecodeRaw, []byte("f")},
{"无填充 3 字符", "Zm8", base64x.DecodeRaw, []byte("fo")},
{"无填充 4 字符", "AAAA", base64x.DecodeRaw, []byte{0x00, 0x00, 0x00}},
{"无填充 7 字符", "SGVsbG8", base64x.DecodeRaw, []byte("Hello")},
{"无填充 URL 7 字符", "SGVsbG8", base64x.DecodeRawURL, []byte("Hello")},
{"带填充 URL 8 字符", "SGVsbG8=", base64x.DecodeURL, []byte("Hello")},
}
for _, c := range ok {
got, err := c.dec(c.in)
if err != nil {
t.Errorf("%s: 解码 %q 意外失败: %v", c.name, c.in, err)
continue
}
if !bytes.Equal(got, c.want) {
t.Errorf("%s: 解码 %q = %x, 期望 %x", c.name, c.in, got, c.want)
}
}
// 应当失败的用例。
failures := []struct {
name string
in string
dec func(string) ([]byte, error)
}{
{"带填充解码器遇到缺失填充", "Zg", base64x.Decode},
{"带填充解码器遇到缺失填充(2)", "Zm8", base64x.Decode},
{"带填充解码器遇到长度非 4 倍数", "SGVsbG8", base64x.Decode},
{"带填充 URL 解码器遇到缺失填充", "Zg", base64x.DecodeURL},
{"带填充 URL 解码器遇到长度非 4 倍数", "SGVsbG8", base64x.DecodeURL},
{"无填充解码器遇到 1 个残留字符", "Z", base64x.DecodeRaw},
{"无填充 URL 解码器遇到 1 个残留字符", "Z", base64x.DecodeRawURL},
{"带填充解码器遇到 1 个残留字符", "Z", base64x.Decode},
{"无填充解码器遇到 5 个字符", "AAAAA", base64x.DecodeRaw},
{"无填充解码器遇到带填充 4 字符", "Zg==", base64x.DecodeRaw},
{"无填充 URL 解码器遇到带填充 4 字符", "Zg==", base64x.DecodeRawURL},
}
for _, c := range failures {
got, err := c.dec(c.in)
if err == nil {
t.Errorf("%s: 解码 %q 应当失败,却成功得到 %x", c.name, c.in, got)
}
if got != nil {
t.Errorf("%s: 解码 %q 失败时应返回 nil,实际 %x", c.name, c.in, got)
}
}
}
// TestDecodeEmptyInput 明确各变体对空串的影响(空串是合法的"零长度数据")。
func TestDecodeEmptyInput(t *testing.T) {
for _, v := range variants {
decoded, err := v.decode("")
if err != nil {
t.Errorf("%s 解码空串应成功: %v", v.name, err)
continue
}
if len(decoded) != 0 {
t.Errorf("%s 解码空串得到 %d 字节,期望 0", v.name, len(decoded))
}
}
}
// TestCrossCheckWithStdlib 与标准库交叉验证,确保封装没有引入行为差异。
func TestCrossCheckWithStdlib(t *testing.T) {
payload := []byte("cross check with encoding/base64")
if got, want := base64x.Encode(payload), base64.StdEncoding.EncodeToString(payload); got != want {
t.Errorf("Encode = %q, 标准库 = %q", got, want)
}
if got, want := base64x.EncodeURL(payload), base64.URLEncoding.EncodeToString(payload); got != want {
t.Errorf("EncodeURL = %q, 标准库 = %q", got, want)
}
if got, want := base64x.EncodeRaw(payload), base64.RawStdEncoding.EncodeToString(payload); got != want {
t.Errorf("EncodeRaw = %q, 标准库 = %q", got, want)
}
if got, want := base64x.EncodeRawURL(payload), base64.RawURLEncoding.EncodeToString(payload); got != want {
t.Errorf("EncodeRawURL = %q, 标准库 = %q", got, want)
}
decoded, err := base64x.Decode(base64.StdEncoding.EncodeToString(payload))
if err != nil || !bytes.Equal(decoded, payload) {
t.Errorf("Decode 与标准库编码互操作失败: %v", err)
}
}
// TestNoPanicOnAnyInput 用一批畸形输入整体验证解码与编码都不 panic。
func TestNoPanicOnAnyInput(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("畸形输入导致 panic: %v", r)
}
}()
inputs := []string{"", "=", "==", "====", "A", "AA", "AAA", "AAAA", "\x00\x01", strings.Repeat("=", 100), strings.Repeat("A", 1000)}
for _, in := range inputs {
_, _ = base64x.Decode(in)
_, _ = base64x.DecodeURL(in)
_, _ = base64x.DecodeRaw(in)
_, _ = base64x.DecodeRawURL(in)
_ = base64x.Encode([]byte(in))
_ = base64x.EncodeRaw([]byte(in))
}
}
// isASCII 判断字符串是否只含 ASCII 可打印字符。
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] < 0x20 || s[i] > 0x7e {
return false
}
}
return true
}