完善不同加密方法的使用

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
+177
View File
@@ -0,0 +1,177 @@
package encryptx_test
import (
"strings"
"testing"
"code.yun.ink/pkg/encryptx"
)
// 本文件提供 Go 原生 fuzz 目标。它们有两个作用:
//
// 1. 普通 `go test` 时以种子语料运行,作为常规回归测试;
// 2. `go test -fuzz=FuzzXxx` 时持续变异输入,穷举解码/解密路径上的边界。
//
// 之所以重点 fuzz 解密路径:这些函数的输入直接来自网络或数据库等不可信来源,
// 且历史上正是在这里出现过 "slice bounds out of range" 崩溃。
//
// 有意排除 RSA 与 PBKDF2/scrypt/Argon2:它们单次调用开销过大,会让 fuzz 的
// 变异吞吐低到没有意义。这些算法的错误路径由各自的单元测试覆盖。
// fuzzTargets 是适合高频 fuzz 的算法:对称加密与编码。
func fuzzTargets() []encryptx.EncryptType {
targets := make([]encryptx.EncryptType, 0, len(symmetricTypes)+3)
targets = append(targets, symmetricTypes...)
targets = append(targets, encryptx.BASE64, encryptx.BASE64_URL, encryptx.HEX)
return targets
}
// precomputedKeys 预生成各算法的密钥,避免每次 fuzz 迭代重复生成。
// 不需要密钥的算法(编码类)不参与。
func precomputedKeys(f *testing.F) map[encryptx.EncryptType][]byte {
f.Helper()
keys := make(map[encryptx.EncryptType][]byte)
for _, encryptType := range fuzzTargets() {
if !encryptType.NeedsKey() {
continue
}
key, err := encryptx.GenerateKey(encryptType, nil)
if err != nil {
f.Fatalf("%s: GenerateKey 失败: %v", encryptType, err)
}
keys[encryptType] = key
}
return keys
}
// FuzzDecryptNeverPanics 是 P0-3 的模糊测试形态。
//
// 无论喂进什么字符串,解密都必须安全返回,绝不能因为切片越界、除零或空指针
// 而崩溃——攻击者只要能构造一段畸形的十六进制密文,就能用 panic 打掉服务。
func FuzzDecryptNeverPanics(f *testing.F) {
keys := precomputedKeys(f)
targets := fuzzTargets()
// 种子语料:空、奇数长度、非十六进制、非分组对齐、超长、含分隔符
f.Add("")
f.Add("A")
f.Add("AB")
f.Add("zz")
f.Add(":::")
f.Add("0:0")
f.Add(strings.Repeat("AB", 7))
f.Add(strings.Repeat("AB", 8))
f.Add(strings.Repeat("AB", 9))
f.Add(strings.Repeat("AB", 17))
f.Add(strings.Repeat("FF", 64))
f.Add("not-hex-at-all")
f.Fuzz(func(t *testing.T, data string) {
for _, encryptType := range targets {
// 只要求"不 panic";返回值无论是什么都可以接受。
_, _ = encryptx.Decrypt(encryptType, data, keys[encryptType], nil)
}
})
}
// FuzzDecryptWithIVNeverPanics 覆盖带显式 IV 的解密路径。
//
// IV 由选项传入,长度可能任意;这条路径额外接受一个十六进制字符串,
// 是另一个容易越界的入口。
func FuzzDecryptWithIVNeverPanics(f *testing.F) {
keys := precomputedKeys(f)
// 只挑接受 iv 选项的模式(ECB 不接受)
targets := []encryptx.EncryptType{
encryptx.AES_CBC, encryptx.AES_CFB, encryptx.AES_OFB, encryptx.AES_CTR,
encryptx.DES_CBC, encryptx.TDES_CBC,
}
f.Add("AB", "0011223344556677")
f.Add("", "")
f.Add(strings.Repeat("AB", 20), "zz")
f.Fuzz(func(t *testing.T, data, iv string) {
for _, encryptType := range targets {
_, _ = encryptx.Decrypt(encryptType, data, keys[encryptType],
encryptx.Options{"iv": iv})
}
})
}
// FuzzSymmetricRoundTrip 验证「加密再解密必然还原」这一基本性质对任意明文成立,
// 包括空串、跨分组边界长度与多字节 UTF-8。
func FuzzSymmetricRoundTrip(f *testing.F) {
keys := precomputedKeys(f)
f.Add("")
f.Add("a")
f.Add(strings.Repeat("x", 15))
f.Add(strings.Repeat("x", 16))
f.Add(strings.Repeat("x", 17))
f.Add("中文与 emoji 🔐")
f.Add(strings.Repeat("payload", 100))
f.Fuzz(func(t *testing.T, plaintext string) {
for _, encryptType := range symmetricTypes {
key := keys[encryptType]
ciphertext, err := encryptx.Encrypt(encryptType, plaintext, key, nil)
if err != nil {
t.Fatalf("%s: 加密失败: %v", encryptType, err)
}
decrypted, err := encryptx.Decrypt(encryptType, ciphertext, key, nil)
if err != nil {
t.Fatalf("%s: 解密失败: %v", encryptType, err)
}
if decrypted != plaintext {
t.Fatalf("%s: 往返不一致(明文长度 %d)", encryptType, len(plaintext))
}
}
})
}
// FuzzAEADTamperDetection 验证 AEAD 的认证性质:对密文做任何单字节改动,
// 解密都必须失败。
func FuzzAEADTamperDetection(f *testing.F) {
keys := precomputedKeys(f)
aeads := []encryptx.EncryptType{
encryptx.AES_GCM, encryptx.CHACHA20_POLY1305, encryptx.XCHACHA20_POLY1305,
}
f.Add("payload", 0)
f.Add("", 3)
f.Add(strings.Repeat("x", 200), 42)
f.Fuzz(func(t *testing.T, plaintext string, offset int) {
for _, encryptType := range aeads {
key := keys[encryptType]
ciphertext, err := encryptx.Encrypt(encryptType, plaintext, key, encryptx.Options{"aad": "ctx"})
if err != nil {
t.Fatalf("%s: 加密失败: %v", encryptType, err)
}
if len(ciphertext) == 0 {
continue
}
// 把 offset 映射到合法下标,并避开分隔符
idx := offset % len(ciphertext)
if idx < 0 {
idx += len(ciphertext)
}
if ciphertext[idx] == ':' {
continue
}
tampered := []byte(ciphertext)
if tampered[idx] == '0' {
tampered[idx] = '1'
} else {
tampered[idx] = '0'
}
if _, err := encryptx.Decrypt(encryptType, string(tampered), key,
encryptx.Options{"aad": "ctx"}); err == nil {
t.Fatalf("%s: 篡改第 %d 个字符后仍然解密成功", encryptType, idx)
}
}
})
}