完善不同加密方法的使用
This commit is contained in:
+119
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user