完善不同加密方法的使用

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
+119
View File
@@ -0,0 +1,119 @@
// Package hexx 提供十六进制编解码的语义清晰封装。
//
// 本包基于 encoding/hex,额外提供:
//
// - 大小写混合解码(Decode 语义上接受 "DEADbeef" 这类输入);
// - 严格大写校验解码(DecodeUpper),用于需要规范化格式的场景;
// - 合法性检查(IsValid)与友好的中文错误信息。
//
// 安全注意:十六进制是**编码**而非加密,不提供任何机密性,
// 任何人都能还原原文;密钥、口令、随机数等敏感数据即使转成十六进制
// 也依然是明文,禁止据此认为可以安全地打印或记录到日志。
// 另外十六进制编码会使数据膨胀一倍,解码不可信输入前应限制输入长度。
package hexx
import (
"encoding/hex"
"fmt"
"strings"
)
// Encode 把字节串编码为小写十六进制字符串(长度是输入的两倍)。
//
// 参数:
// - data: 待编码的字节串,可以为 nil(返回空字符串)。
//
// 返回值:仅含 0-9a-f 的小写十六进制字符串。
//
// 安全注意:十六进制不提供机密性,编码后的秘密仍是明文,不要写入日志。
func Encode(data []byte) string {
return hex.EncodeToString(data)
}
// EncodeUpper 把字节串编码为大写十六进制字符串(长度是输入的两倍)。
//
// 参数:
// - data: 待编码的字节串,可以为 nil(返回空字符串)。
//
// 返回值:仅含 0-9A-F 的大写十六进制字符串。
//
// 安全注意:十六进制不提供机密性;大小写不影响解码结果,
// 但如与外部系统约定固定大小写,请统一使用本函数或 Encode。
func EncodeUpper(data []byte) string {
return strings.ToUpper(hex.EncodeToString(data))
}
// Decode 解码十六进制字符串,语义上接受大小写混合的输入。
//
// 参数:
// - s: 待解码的十六进制字符串,长度必须为偶数。
//
// 返回值:解码后的字节串;失败时返回 nil 与包装后的 error。
//
// 安全注意:失败时不返回部分结果;本函数对 "0x" 前缀、空白字符、
// 奇数长度都会报错,便于尽早发现被截断或污染的输入。
func Decode(s string) ([]byte, error) {
decoded, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("十六进制解码失败(长度 %d): %w", len(s), err)
}
return decoded, nil
}
// DecodeUpper 解码十六进制字符串,并要求输入**确实是大写**形式。
//
// 参数:
// - s: 待解码的十六进制字符串,只能包含 0-9 与 A-F,长度必须为偶数。
//
// 返回值:解码后的字节串;包含小写字母或非法字符时返回 nil 与包装后的 error。
//
// 安全注意:用于严格模式(例如校验外部系统给出的规范化大写格式),
// 可避免因大小写不一致导致的比对歧义;普通场景请使用 Decode。
func DecodeUpper(s string) ([]byte, error) {
if strings.ContainsFunc(s, func(r rune) bool {
return r >= 'a' && r <= 'f'
}) {
return nil, fmt.Errorf("十六进制解码(严格大写)失败: 输入包含小写字母")
}
decoded, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("十六进制解码(严格大写)失败(长度 %d): %w", len(s), err)
}
return decoded, nil
}
// IsValid 判断字符串是否为合法的十六进制串。
//
// 参数:
// - s: 待检查的字符串。
//
// 返回值:长度偶数且每个字符都属于 [0-9a-fA-F] 时返回 true;
// 空串、奇数长度、含非法字符或空白时返回 false。
//
// 安全注意:空串被视为非法,避免把"缺失的字段"误判为合法值;
// 本函数只做格式检查,不代表内容可信,来源不明的数据仍需按不可信输入处理。
func IsValid(s string) bool {
if len(s) == 0 || len(s)%2 != 0 {
return false
}
for i := 0; i < len(s); i++ {
if !isHexDigit(s[i]) {
return false
}
}
return true
}
// isHexDigit 判断字节是否为十六进制数字字符(0-9、a-f、A-F)。
func isHexDigit(c byte) bool {
switch {
case c >= '0' && c <= '9':
return true
case c >= 'a' && c <= 'f':
return true
case c >= 'A' && c <= 'F':
return true
default:
return false
}
}
+279
View File
@@ -0,0 +1,279 @@
package hexx_test
import (
"bytes"
"encoding/hex"
"strings"
"testing"
"code.yun.ink/pkg/encryptx/hexx"
)
// payloads 覆盖空、单字节、ASCII、UTF-8 与全字节值等输入。
var payloads = [][]byte{
nil,
{},
{0x00},
{0x0f, 0xf0},
[]byte("Hello, World!"),
[]byte("中文与 emoji 🚀"),
bytes.Repeat([]byte{0xde, 0xad, 0xbe, 0xef}, 64),
func() []byte {
b := make([]byte, 256)
for i := range b {
b[i] = byte(i)
}
return b
}(),
}
// TestRoundTrip 校验大小写两种编码都能往返还原。
func TestRoundTrip(t *testing.T) {
for i, payload := range payloads {
lower := hexx.Encode(payload)
upper := hexx.EncodeUpper(payload)
if len(lower) != len(payload)*2 {
t.Errorf("用例 %d: Encode 长度 = %d, 期望 %d", i, len(lower), len(payload)*2)
}
if lower != strings.ToLower(lower) {
t.Errorf("用例 %d: Encode 应输出小写: %q", i, lower)
}
if upper != strings.ToUpper(upper) {
t.Errorf("用例 %d: EncodeUpper 应输出大写: %q", i, upper)
}
if !strings.EqualFold(lower, upper) {
t.Errorf("用例 %d: 大小写编码内容不一致: %q vs %q", i, lower, upper)
}
decodedLower, err := hexx.Decode(lower)
if err != nil {
t.Fatalf("用例 %d: Decode(小写) 失败: %v", i, err)
}
if !bytes.Equal(decodedLower, payload) {
t.Errorf("用例 %d: Decode(小写) 往返不一致", i)
}
decodedUpper, err := hexx.DecodeUpper(upper)
if err != nil {
t.Fatalf("用例 %d: DecodeUpper(大写) 失败: %v", i, err)
}
if !bytes.Equal(decodedUpper, payload) {
t.Errorf("用例 %d: DecodeUpper 往返不一致", i)
}
// 大写编码也必须能被宽松的 Decode 正确解码。
decodedLoose, err := hexx.Decode(upper)
if err != nil {
t.Fatalf("用例 %d: Decode(大写) 失败: %v", i, err)
}
if !bytes.Equal(decodedLoose, payload) {
t.Errorf("用例 %d: Decode(大写) 往返不一致", i)
}
}
}
// TestDecodeMixedCase 校验 Decode 在语义上接受大小写混合输入。
func TestDecodeMixedCase(t *testing.T) {
cases := []struct {
in string
want []byte
}{
{"deadbeef", []byte{0xde, 0xad, 0xbe, 0xef}},
{"DEADBEEF", []byte{0xde, 0xad, 0xbe, 0xef}},
{"DeAdBeEf", []byte{0xde, 0xad, 0xbe, 0xef}},
{"dEaDbEeF", []byte{0xde, 0xad, 0xbe, 0xef}},
{"0a1B2c3D", []byte{0x0a, 0x1b, 0x2c, 0x3d}},
}
for _, c := range cases {
got, err := hexx.Decode(c.in)
if err != nil {
t.Errorf("Decode(%q) 返回错误: %v", c.in, err)
continue
}
if !bytes.Equal(got, c.want) {
t.Errorf("Decode(%q) = %x, 期望 %x", c.in, got, c.want)
}
}
}
// TestEncodeMatchesStdlib 与标准库交叉验证,确保封装未引入行为差异。
func TestEncodeMatchesStdlib(t *testing.T) {
for i, payload := range payloads {
if got, want := hexx.Encode(payload), hex.EncodeToString(payload); got != want {
t.Errorf("用例 %d: Encode = %q, 标准库 = %q", i, got, want)
}
if got, want := hexx.EncodeUpper(payload), strings.ToUpper(hex.EncodeToString(payload)); got != want {
t.Errorf("用例 %d: EncodeUpper = %q, 标准库 = %q", i, got, want)
}
}
}
// TestIsValid 校验 IsValid 对合法与非法输入的判定。
func TestIsValid(t *testing.T) {
valid := []string{
"00", "ff", "FF", "deadbeef", "DEADBEEF", "DeAdBeEf",
"0123456789abcdefABCDEF",
}
for _, s := range valid {
if !hexx.IsValid(s) {
t.Errorf("IsValid(%q) = false, 期望 true", s)
}
}
invalid := []struct {
name string
in string
}{
{"空串", ""},
{"奇数长度", "a"},
{"奇数长度三字符", "abc"},
{"奇数长度长串", strings.Repeat("ab", 20) + "c"},
{"非十六进制字母", "zz"},
{"含 g", "0g"},
{"含 0x 前缀", "0x1f"},
{"含空格", "de ad"},
{"含换行", "dead\n"},
{"含连字符", "de-ad"},
{"非 ASCII 字符", "中文"},
{"emoji", "🚀"},
{"含 tab", "aa\tbb"},
}
for _, c := range invalid {
if hexx.IsValid(c.in) {
t.Errorf("IsValid(%q)(%s)= true, 期望 false", c.in, c.name)
}
}
}
// TestDecodeRejectsInvalid 校验 Decode 对非法输入返回 nil 与 error。
func TestDecodeRejectsInvalid(t *testing.T) {
invalid := []struct {
name string
in string
}{
{"奇数长度", "abc"},
{"奇数长度单字符", "a"},
{"非十六进制字符", "zz"},
{"包含 g", "0g"},
{"0x 前缀", "0x1f"},
{"含空格", "de ad"},
{"含换行", "beef\n"},
{"含连字符", "de-ad"},
{"非 ASCII", "中文"},
{"emoji", "🚀"},
{"长度与内容都非法", "xyz"},
}
for _, c := range invalid {
got, err := hexx.Decode(c.in)
if err == nil {
t.Errorf("Decode(%q)(%s)应当失败,却成功得到 %x", c.in, c.name, got)
}
if got != nil {
t.Errorf("Decode(%q)(%s)失败时应返回 nil,实际 %x", c.in, c.name, got)
}
if err != nil && err.Error() == "" {
t.Errorf("Decode(%q) 返回了空错误信息", c.in)
}
}
}
// TestDecodeUpperStrict 校验 DecodeUpper 在语义上要求输入是大写形式。
func TestDecodeUpperStrict(t *testing.T) {
// 纯大写(含数字)必须成功。
uppers := []struct {
in string
want []byte
}{
{"DEADBEEF", []byte{0xde, 0xad, 0xbe, 0xef}},
{"00FF", []byte{0x00, 0xff}},
{"0A1B2C3D", []byte{0x0a, 0x1b, 0x2c, 0x3d}},
}
for _, c := range uppers {
got, err := hexx.DecodeUpper(c.in)
if err != nil {
t.Errorf("DecodeUpper(%q) 返回错误: %v", c.in, err)
continue
}
if !bytes.Equal(got, c.want) {
t.Errorf("DecodeUpper(%q) = %x, 期望 %x", c.in, got, c.want)
}
}
// 含小写字母的输入必须被拒绝,并返回 nil。
rejected := []string{"deadbeef", "DeadBeef", "DEADbeef", "de"}
for _, in := range rejected {
got, err := hexx.DecodeUpper(in)
if err == nil {
t.Errorf("DecodeUpper(%q) 应当失败(含小写字母),却成功得到 %x", in, got)
}
if got != nil {
t.Errorf("DecodeUpper(%q) 失败时应返回 nil,实际 %x", in, got)
}
}
// 大写但仍非法的输入同样必须失败。
for _, in := range []string{"0X1F", "GG", "ABC", "DE AD"} {
got, err := hexx.DecodeUpper(in)
if err == nil {
t.Errorf("DecodeUpper(%q) 应当失败,却成功得到 %x", in, got)
}
if got != nil {
t.Errorf("DecodeUpper(%q) 失败时应返回 nil,实际 %x", in, got)
}
}
}
// TestDecodeEmpty 校验空串的边界语义。
func TestDecodeEmpty(t *testing.T) {
got, err := hexx.Decode("")
if err != nil {
t.Errorf("Decode(\"\") 应成功: %v", err)
}
if len(got) != 0 {
t.Errorf("Decode(\"\") 得到 %d 字节,期望 0", len(got))
}
// IsValid 明确把空串视为非法。
if hexx.IsValid("") {
t.Error("IsValid(\"\") 应为 false")
}
}
// TestNoPanicOnAnyInput 用一批畸形输入整体验证不 panic。
func TestNoPanicOnAnyInput(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("畸形输入导致 panic: %v", r)
}
}()
inputs := []string{
"", "0", "00", "000", strings.Repeat("f", 511), strings.Repeat("F", 512),
"\x00\x01\x02", "%$#@!", "ff", "🇨🇳", "AB",
}
for _, in := range inputs {
_ = hexx.IsValid(in)
_, _ = hexx.Decode(in)
_, _ = hexx.DecodeUpper(in)
_ = hexx.Encode([]byte(in))
_ = hexx.EncodeUpper([]byte(in))
}
}
// TestEncodeDecodeNil 校验 nil 输入的处理。
func TestEncodeDecodeNil(t *testing.T) {
if got := hexx.Encode(nil); got != "" {
t.Errorf("Encode(nil) = %q, 期望空串", got)
}
if got := hexx.EncodeUpper(nil); got != "" {
t.Errorf("EncodeUpper(nil) = %q, 期望空串", got)
}
got, err := hexx.Decode(hexx.Encode(nil))
if err != nil {
t.Errorf("Decode(Encode(nil)) 失败: %v", err)
}
if len(got) != 0 {
t.Errorf("Decode(Encode(nil)) 得到 %d 字节,期望 0", len(got))
}
}