Files
encryptx/encryptx_test.go
T

1069 lines
38 KiB
Go
Raw Normal View History

2026-09-30 15:49:12 +08:00
package encryptx_test
2025-11-03 23:40:41 +08:00
import (
2026-09-30 15:49:12 +08:00
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
2025-11-03 23:40:41 +08:00
"testing"
2026-09-30 15:49:12 +08:00
"code.yun.ink/pkg/encryptx"
"code.yun.ink/pkg/encryptx/rsax"
2025-11-03 23:40:41 +08:00
)
2026-09-30 15:49:12 +08:00
// 所有对称加密算法(含 AEAD),用于表驱动测试。
var symmetricTypes = []encryptx.EncryptType{
encryptx.DES_ECB, encryptx.DES_CBC, encryptx.DES_CFB, encryptx.DES_OFB, encryptx.DES_CTR,
encryptx.TDES_ECB, encryptx.TDES_CBC, encryptx.TDES_CFB, encryptx.TDES_OFB, encryptx.TDES_CTR,
encryptx.AES_ECB, encryptx.AES_CBC, encryptx.AES_CFB, encryptx.AES_OFB, encryptx.AES_CTR,
encryptx.AES_GCM,
encryptx.BLOWFISH_ECB, encryptx.BLOWFISH_CBC, encryptx.BLOWFISH_CFB,
encryptx.BLOWFISH_OFB, encryptx.BLOWFISH_CTR,
encryptx.CHACHA20_POLY1305, encryptx.XCHACHA20_POLY1305,
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
var hashTypes = []encryptx.EncryptType{
encryptx.MD5, encryptx.SHA1, encryptx.SHA224, encryptx.SHA256, encryptx.SHA384, encryptx.SHA512,
encryptx.SHA3_224, encryptx.SHA3_256, encryptx.SHA3_384, encryptx.SHA3_512,
encryptx.BLAKE2B_256, encryptx.BLAKE2S_256, encryptx.RIPEMD160,
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
var hmacTypes = []encryptx.EncryptType{
encryptx.HMAC_SHA1, encryptx.HMAC_SHA256, encryptx.HMAC_SHA384, encryptx.HMAC_SHA512,
}
var kdfTypes = []encryptx.EncryptType{
encryptx.PBKDF2, encryptx.HKDF, encryptx.SCRYPT, encryptx.ARGON2ID,
}
// ---- 枚举元数据 ----
func TestEncryptTypeMetadata(t *testing.T) {
// 历史数值必须保持不变,否则已落库的配置会指向错误的算法。
legacyValues := map[encryptx.EncryptType]int{
encryptx.MD5: 0, encryptx.SHA1: 1, encryptx.SHA256: 2, encryptx.SHA512: 3,
encryptx.SHA3_256: 4, encryptx.SHA3_384: 5, encryptx.SHA3_512: 6,
encryptx.DES_ECB: 7, encryptx.DES_CBC: 8, encryptx.DES_CFB: 9,
encryptx.TDES_ECB: 10, encryptx.TDES_CBC: 11, encryptx.TDES_CFB: 12,
encryptx.AES_ECB: 13, encryptx.AES_CBC: 14, encryptx.AES_CFB: 15, encryptx.AES_GCM: 16,
encryptx.BLOWFISH_ECB: 17, encryptx.BLOWFISH_CBC: 18, encryptx.BLOWFISH_CFB: 19,
encryptx.RSA: 20, encryptx.HMAC_SHA256: 21, encryptx.HMAC_SHA512: 22,
encryptx.PBKDF2: 23, encryptx.BASE64: 24,
}
for encryptType, want := range legacyValues {
if got := int(encryptType); got != want {
t.Errorf("%s 的数值 = %d, 期望 %d(破坏兼容性)", encryptType, got, want)
2025-11-03 23:40:41 +08:00
}
}
}
2026-09-30 15:49:12 +08:00
func TestGetAllEncryptTypes(t *testing.T) {
types := encryptx.GetAllEncryptTypes()
if len(types) < 40 {
t.Errorf("算法数量 = %d, 期望至少 40 个", len(types))
}
for i := 1; i < len(types); i++ {
if types[i-1] >= types[i] {
t.Errorf("GetAllEncryptTypes 未按升序返回: 位置 %d", i)
}
}
for _, encryptType := range types {
if !encryptType.Valid() {
t.Errorf("%d.Valid() = false", int(encryptType))
}
if encryptType.String() == "" {
t.Errorf("%d 的名称为空", int(encryptType))
}
}
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
func TestSecurityMetadata(t *testing.T) {
// 已被攻破的算法必须被标记为 SecurityBroken
for _, encryptType := range []encryptx.EncryptType{encryptx.MD5, encryptx.SHA1,
encryptx.DES_ECB, encryptx.DES_CBC, encryptx.DES_CFB} {
if got := encryptType.SecurityLevel(); got != encryptx.SecurityBroken {
t.Errorf("%s 的安全等级 = %v, 期望已攻破", encryptType, got)
}
if !encryptType.IsDeprecated() {
t.Errorf("%s 应当被标记为已弃用", encryptType)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// 推荐的现代算法
for _, encryptType := range []encryptx.EncryptType{encryptx.AES_GCM, encryptx.CHACHA20_POLY1305,
encryptx.XCHACHA20_POLY1305, encryptx.SHA256, encryptx.SHA3_256, encryptx.ARGON2ID} {
if got := encryptType.SecurityLevel(); got != encryptx.SecurityRecommended {
t.Errorf("%s 的安全等级 = %v, 期望推荐", encryptType, got)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// 只有 AEAD 类算法才应标记完整性保护
if !encryptx.AES_GCM.IsAEAD() || !encryptx.CHACHA20_POLY1305.IsAEAD() {
t.Error("AEAD 算法未被标记为提供完整性保护")
}
if encryptx.AES_CBC.IsAEAD() {
t.Error("AES-CBC 不应被标记为提供完整性保护")
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// 哈希不可逆、不需要密钥
for _, encryptType := range hashTypes {
if encryptType.IsReversible() {
t.Errorf("%s 不应可逆", encryptType)
}
if encryptType.NeedsKey() {
t.Errorf("%s 不应需要密钥", encryptType)
}
}
}
func TestUnsupportedType(t *testing.T) {
bogus := encryptx.EncryptType(9999)
if bogus.Valid() {
t.Error("EncryptType(9999).Valid() = true")
}
if _, err := encryptx.Encrypt(bogus, "x", nil, nil); !errors.Is(err, encryptx.ErrUnsupportedType) {
t.Errorf("Encrypt: 期望 ErrUnsupportedType, 得到 %v", err)
}
if _, err := encryptx.Decrypt(bogus, "x", nil, nil); !errors.Is(err, encryptx.ErrUnsupportedType) {
t.Errorf("Decrypt: 期望 ErrUnsupportedType, 得到 %v", err)
}
if _, err := encryptx.GenerateKey(bogus, nil); !errors.Is(err, encryptx.ErrUnsupportedType) {
t.Errorf("GenerateKey: 期望 ErrUnsupportedType, 得到 %v", err)
}
}
// ---- 哈希 ----
func TestHashAlgorithms(t *testing.T) {
// 权威测试向量(与各子包测试一致,这里验证门面接线正确)
vectors := map[encryptx.EncryptType]string{
encryptx.MD5: "900150983cd24fb0d6963f7d28e17f72",
encryptx.SHA1: "a9993e364706816aba3e25717850c26c9cd0d89d",
encryptx.SHA224: "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
encryptx.SHA256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
encryptx.SHA384: "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
encryptx.SHA512: "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
encryptx.SHA3_224: "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf",
encryptx.SHA3_256: "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532",
encryptx.SHA3_384: "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25",
encryptx.SHA3_512: "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0",
encryptx.BLAKE2B_256: "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319",
encryptx.BLAKE2S_256: "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982",
encryptx.RIPEMD160: "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc",
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
for encryptType, want := range vectors {
got, err := encryptx.Encrypt(encryptType, "abc", nil, nil)
if err != nil {
t.Errorf("%s: 计算失败: %v", encryptType, err)
continue
}
if got != want {
t.Errorf("%s(\"abc\") = %s, 期望 %s", encryptType, got, want)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// 哈希不接受任何选项
if _, err := encryptx.Encrypt(encryptx.SHA256, "x", nil, encryptx.Options{"bogus": 1}); !errors.Is(err, encryptx.ErrUnknownOption) {
t.Errorf("哈希传入未知选项: 期望 ErrUnknownOption, 得到 %v", err)
}
}
func TestHashAlgorithmsProduceDistinctOutput(t *testing.T) {
seen := make(map[string]encryptx.EncryptType)
for _, encryptType := range hashTypes {
got, err := encryptx.Encrypt(encryptType, "same input", nil, nil)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if prev, dup := seen[got]; dup {
t.Errorf("%s 与 %s 产生了相同摘要", encryptType, prev)
}
seen[got] = encryptType
}
}
// ---- HMAC ----
func TestHMACAlgorithms(t *testing.T) {
// RFC 4231 Test Case 1:key = 20 字节 0x0b,data = "Hi There"
key := bytes.Repeat([]byte{0x0b}, 20)
vectors := map[encryptx.EncryptType]string{
encryptx.HMAC_SHA256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
encryptx.HMAC_SHA512: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
}
for encryptType, want := range vectors {
got, err := encryptx.Encrypt(encryptType, "Hi There", key, nil)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if got != want {
t.Errorf("%s = %s, 期望 %s", encryptType, got, want)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// RFC 2202 未直接给出的 SHA-1/SHA-384 分支至少要有正确长度
for _, tc := range []struct {
encryptType encryptx.EncryptType
hexLength int
}{
{encryptx.HMAC_SHA1, 40}, {encryptx.HMAC_SHA384, 96},
} {
got, err := encryptx.Encrypt(tc.encryptType, "Hi There", key, nil)
if err != nil {
t.Fatalf("%s: %v", tc.encryptType, err)
}
if len(got) != tc.hexLength {
t.Errorf("%s 长度 = %d, 期望 %d", tc.encryptType, len(got), tc.hexLength)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// HMAC 需要密钥
if _, err := encryptx.Encrypt(encryptx.HMAC_SHA256, "x", nil, nil); !errors.Is(err, encryptx.ErrMissingKey) {
t.Errorf("HMAC 无密钥: 期望 ErrMissingKey, 得到 %v", err)
}
}
// ---- 编码 ----
func TestEncodingAlgorithms(t *testing.T) {
original := "hello 世界 + / = 编码"
cases := []struct {
encryptType encryptx.EncryptType
}{
{encryptx.BASE64},
{encryptx.BASE64_URL},
{encryptx.HEX},
}
for _, c := range cases {
encoded, err := encryptx.Encrypt(c.encryptType, original, nil, nil)
if err != nil {
t.Fatalf("%s 编码失败: %v", c.encryptType, err)
}
decoded, err := encryptx.Decrypt(c.encryptType, encoded, nil, nil)
if err != nil {
t.Fatalf("%s 解码失败: %v", c.encryptType, err)
}
if decoded != original {
t.Errorf("%s 往返不一致: 得到 %q, 期望 %q", c.encryptType, decoded, original)
}
}
// 非法输入必须报错而不是返回垃圾
if _, err := encryptx.Decrypt(encryptx.HEX, "这不是十六进制", nil, nil); !errors.Is(err, encryptx.ErrInvalidInput) {
t.Errorf("非法 HEX: 期望 ErrInvalidInput, 得到 %v", err)
}
if _, err := encryptx.Decrypt(encryptx.BASE64, "!!!!", nil, nil); !errors.Is(err, encryptx.ErrInvalidInput) {
t.Errorf("非法 Base64: 期望 ErrInvalidInput, 得到 %v", err)
}
}
// ---- 对称加密 ----
// TestGenerateKeyThenEncrypt 是 P0-2 的核心回归测试。
//
// 历史缺陷:GenerateKey 返回十六进制文本(32 字节 → 64 字符),而加密函数把它
// 当作原始密钥字节使用,于是「生成密钥 → 加密」必然失败,报
// "crypto/aes: invalid key size 64"。现在密钥统一为 []byte,这条路径必须畅通。
func TestGenerateKeyThenEncrypt(t *testing.T) {
const plaintext = "生成密钥后直接加密"
for _, encryptType := range symmetricTypes {
key, err := encryptx.GenerateKey(encryptType, nil)
if err != nil {
t.Errorf("%s: GenerateKey 失败: %v", encryptType, err)
continue
}
if len(key) == 0 {
t.Errorf("%s: GenerateKey 返回空密钥", encryptType)
continue
}
ciphertext, err := encryptx.Encrypt(encryptType, plaintext, key, nil)
if err != nil {
t.Errorf("%s: 用生成的密钥加密失败: %v", encryptType, err)
continue
}
decrypted, err := encryptx.Decrypt(encryptType, ciphertext, key, nil)
if err != nil {
t.Errorf("%s: 解密失败: %v", encryptType, err)
continue
}
if decrypted != plaintext {
t.Errorf("%s: 往返不一致, 得到 %q", encryptType, decrypted)
}
}
}
func TestSymmetricRoundTripVariousLengths(t *testing.T) {
plaintexts := []string{"", "a", "0123456789abcde", "0123456789abcdef",
"0123456789abcdefg", strings.Repeat("x", 5000), "中文与 emoji 🔐"}
for _, encryptType := range symmetricTypes {
key, err := encryptx.GenerateKey(encryptType, nil)
if err != nil {
t.Fatalf("%s: GenerateKey 失败: %v", encryptType, err)
}
for i, plaintext := range plaintexts {
ciphertext, err := encryptx.Encrypt(encryptType, plaintext, key, nil)
if err != nil {
t.Fatalf("%s 明文#%d: 加密失败: %v", encryptType, i, err)
}
decrypted, err := encryptx.Decrypt(encryptType, ciphertext, key, nil)
if err != nil {
t.Fatalf("%s 明文#%d: 解密失败: %v", encryptType, i, err)
}
if decrypted != plaintext {
t.Errorf("%s 明文#%d: 往返不一致", encryptType, i)
2025-11-03 23:40:41 +08:00
}
}
}
}
2026-09-30 15:49:12 +08:00
// TestSymmetricOutputHasNoWhitespace 是 P1-6 的回归测试。
//
// 旧实现在 AES-GCM 的输出里用 fmt.Sprintf("%x\n", ...) 带上了换行符,再靠忽略
// hex.DecodeString 的错误才勉强往返成功。现在输出必须干净。
func TestSymmetricOutputHasNoWhitespace(t *testing.T) {
for _, encryptType := range symmetricTypes {
key, _ := encryptx.GenerateKey(encryptType, nil)
ciphertext, err := encryptx.Encrypt(encryptType, "check format", key, nil)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if strings.ContainsAny(ciphertext, "\n\r\t ") {
t.Errorf("%s 的密文含空白字符: %q", encryptType, ciphertext)
}
if encryptType == encryptx.AES_GCM ||
encryptType == encryptx.CHACHA20_POLY1305 ||
encryptType == encryptx.XCHACHA20_POLY1305 {
if parts := strings.Split(ciphertext, ":"); len(parts) != 2 {
t.Errorf("%s 的密文格式应为 \"密文:nonce\", 得到 %q", encryptType, ciphertext)
}
}
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
func TestSymmetricWrongKeyDoesNotReturnPlaintext(t *testing.T) {
const plaintext = "secret payload"
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
for _, encryptType := range symmetricTypes {
key, _ := encryptx.GenerateKey(encryptType, nil)
otherKey, _ := encryptx.GenerateKey(encryptType, nil)
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
ciphertext, err := encryptx.Encrypt(encryptType, plaintext, key, nil)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
// 允许返回错误,也允许返回垃圾,但绝不能还原出正确明文
decrypted, err := encryptx.Decrypt(encryptType, ciphertext, otherKey, nil)
if err == nil && decrypted == plaintext {
t.Errorf("%s: 错误密钥解出了正确明文", encryptType)
}
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
func TestSymmetricWrongKeySizeRejected(t *testing.T) {
// 57 字节对 DES(8)、3DES(24)、AES(16/24/32)、Blowfish(≤56) 全都非法。
// 注意不能用"短密钥"来测 Blowfish:它接受 4..56 字节的任意长度。
tooLong := bytes.Repeat([]byte{0x01}, 57)
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
for _, encryptType := range symmetricTypes {
if _, err := encryptx.Encrypt(encryptType, "x", tooLong, nil); err == nil {
t.Errorf("%s: 57 字节的密钥应当被拒绝", encryptType)
}
// 空密钥等同于未提供密钥
if _, err := encryptx.Encrypt(encryptType, "x", nil, nil); !errors.Is(err, encryptx.ErrMissingKey) {
t.Errorf("%s: 空密钥期望 ErrMissingKey, 得到 %v", encryptType, err)
}
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
// TestSymmetricTamperDetection 验证带完整性保护的算法能发现篡改。
func TestSymmetricTamperDetection(t *testing.T) {
for _, encryptType := range []encryptx.EncryptType{
encryptx.AES_GCM, encryptx.CHACHA20_POLY1305, encryptx.XCHACHA20_POLY1305,
} {
key, _ := encryptx.GenerateKey(encryptType, nil)
ciphertext, err := encryptx.Encrypt(encryptType, "authenticated payload", key, nil)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
// 逐个字符翻转,必须全部被检出
for i := 0; i < len(ciphertext); i++ {
if ciphertext[i] == ':' {
continue
}
tampered := []byte(ciphertext)
if tampered[i] == '0' {
tampered[i] = '1'
} else {
tampered[i] = '0'
}
if _, err := encryptx.Decrypt(encryptType, string(tampered), key, nil); err == nil {
t.Errorf("%s: 篡改第 %d 个字符后仍然解密成功", encryptType, i)
}
}
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
func TestAEADAADBinding(t *testing.T) {
for _, encryptType := range []encryptx.EncryptType{encryptx.AES_GCM, encryptx.CHACHA20_POLY1305} {
key, _ := encryptx.GenerateKey(encryptType, nil)
aad := encryptx.Options{"aad": "user=42"}
ciphertext, err := encryptx.Encrypt(encryptType, "payload", key, aad)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if _, err := encryptx.Decrypt(encryptType, ciphertext, key, aad); err != nil {
t.Errorf("%s: 正确 AAD 解密失败: %v", encryptType, err)
}
if _, err := encryptx.Decrypt(encryptType, ciphertext, key, encryptx.Options{"aad": "user=43"}); err == nil {
t.Errorf("%s: 错误 AAD 竟然解密成功", encryptType)
}
if _, err := encryptx.Decrypt(encryptType, ciphertext, key, nil); err == nil {
t.Errorf("%s: 缺失 AAD 竟然解密成功", encryptType)
}
}
}
func TestExplicitIVOption(t *testing.T) {
// 只有 CBC/CFB/OFB/CTR 接受显式 IV;ECB 不接受。
cases := []encryptx.EncryptType{
encryptx.AES_CBC, encryptx.AES_CFB, encryptx.AES_OFB, encryptx.AES_CTR,
encryptx.DES_CBC, encryptx.TDES_CBC,
}
for _, encryptType := range cases {
key, _ := encryptx.GenerateKey(encryptType, nil)
// AES 的 IV 是 16 字节,DES/3DES 是 8 字节
ivLen := 16
if encryptType == encryptx.DES_CBC || encryptType == encryptx.TDES_CBC {
ivLen = 8
}
ivHex := strings.Repeat("0011223344556677", 1)
if ivLen == 16 {
ivHex = strings.Repeat("0011223344556677", 2)
}
options := encryptx.Options{"iv": ivHex}
ciphertext, err := encryptx.Encrypt(encryptType, "fixed iv", key, options)
if err != nil {
t.Fatalf("%s: 带显式 IV 加密失败: %v", encryptType, err)
}
decrypted, err := encryptx.Decrypt(encryptType, ciphertext, key, options)
if err != nil {
t.Fatalf("%s: 带显式 IV 解密失败: %v", encryptType, err)
}
if decrypted != "fixed iv" {
t.Errorf("%s: 往返不一致", encryptType)
}
// 相同 IV 下相同明文应产生相同密文(这正说明调用方需自行保证 IV 唯一)
again, _ := encryptx.Encrypt(encryptType, "fixed iv", key, options)
if again != ciphertext {
t.Errorf("%s: 固定 IV 下加密结果不确定", encryptType)
}
// IV 长度不对必须报错
if _, err := encryptx.Encrypt(encryptType, "x", key, encryptx.Options{"iv": "00"}); err == nil {
t.Errorf("%s: 非法 IV 长度应当报错", encryptType)
}
}
// ECB 不接受 IV
key, _ := encryptx.GenerateKey(encryptx.AES_ECB, nil)
if _, err := encryptx.Encrypt(encryptx.AES_ECB, "x", key,
encryptx.Options{"iv": strings.Repeat("00", 16)}); err == nil {
t.Error("ECB 不应接受 IV")
}
}
// ---- 密钥派生 ----
// TestPBKDF2RequiresSalt 是 P0-4 的回归测试。
//
// 历史缺陷:未提供盐值时门面会自动生成随机盐,却只返回密钥、丢弃盐值,导致
// 派生结果永远无法复现。现在要么显式给盐,要么用 DeriveKey 一并拿回盐值。
func TestPBKDF2RequiresSalt(t *testing.T) {
if _, err := encryptx.Encrypt(encryptx.PBKDF2, "password", nil, nil); !errors.Is(err, encryptx.ErrMissingOption) {
t.Errorf("PBKDF2 缺少盐值: 期望 ErrMissingOption, 得到 %v", err)
}
}
func TestDeriveKeyReproducible(t *testing.T) {
password := []byte("correct horse battery staple")
for _, encryptType := range kdfTypes {
// 用很小的参数,保证测试快速
options := fastKDFOptions(encryptType)
key1, salt, err := encryptx.DeriveKey(encryptType, password, options)
if err != nil {
t.Fatalf("%s: DeriveKey 失败: %v", encryptType, err)
}
if len(key1) == 0 {
t.Fatalf("%s: 派生出的密钥为空", encryptType)
}
// 用返回的盐值重新派生,结果必须完全一致——这才是"可用"的密钥派生
withSalt := encryptx.Options{}
for k, v := range options {
withSalt[k] = v
}
withSalt["salt"] = salt
key2, _, err := encryptx.DeriveKey(encryptType, password, withSalt)
if err != nil {
t.Fatalf("%s: 用返回的盐值重新派生失败: %v", encryptType, err)
}
if !bytes.Equal(key1, key2) {
t.Errorf("%s: 派生结果不可复现", encryptType)
}
// 不同的盐必须产生不同的密钥(HKDF 允许空盐,故跳过该断言)
if encryptType != encryptx.HKDF {
otherOptions := encryptx.Options{}
for k, v := range options {
otherOptions[k] = v
}
otherOptions["salt"] = bytes.Repeat([]byte{0xAA}, len(salt)+1)
key3, _, err := encryptx.DeriveKey(encryptType, password, otherOptions)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if bytes.Equal(key1, key3) {
t.Errorf("%s: 不同盐值产生了相同密钥", encryptType)
}
}
}
}
func TestDeriveKeyRejectsEmptySecret(t *testing.T) {
for _, encryptType := range kdfTypes {
if _, _, err := encryptx.DeriveKey(encryptType, nil, fastKDFOptions(encryptType)); !errors.Is(err, encryptx.ErrInvalidInput) {
t.Errorf("%s: 空口令应被拒绝, 得到 %v", encryptType, err)
}
}
}
// TestEncryptKDFWithExplicitSalt 验证 Encrypt 走 KDF 时结果可复现。
//
// 不含 ARGON2ID:它在 Encrypt 路径上是 PHC 口令哈希(自带盐),原始密钥派生
// 请走 DeriveKey。
func TestEncryptKDFWithExplicitSalt(t *testing.T) {
salt := bytes.Repeat([]byte{0x5A}, 16)
for _, encryptType := range []encryptx.EncryptType{encryptx.PBKDF2, encryptx.SCRYPT} {
options := fastKDFOptions(encryptType)
options["salt"] = salt
first, err := encryptx.Encrypt(encryptType, "password", nil, options)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
second, err := encryptx.Encrypt(encryptType, "password", nil, options)
if err != nil {
t.Fatalf("%s: %v", encryptType, err)
}
if first != second {
t.Errorf("%s: 同一盐值下派生结果不可复现", encryptType)
}
}
}
// fastKDFOptions 返回各 KDF 的最小可用参数,避免测试耗时过长。
func fastKDFOptions(encryptType encryptx.EncryptType) encryptx.Options {
switch encryptType {
case encryptx.PBKDF2:
return encryptx.Options{"iterations": 10000, "keyLength": 32}
case encryptx.SCRYPT:
return encryptx.Options{"n": 1 << 10, "r": 8, "p": 1, "keyLength": 32}
case encryptx.ARGON2ID:
return encryptx.Options{"time": 1, "memory": 8 * 1024, "threads": 1, "keyLength": 32}
default:
return encryptx.Options{"keyLength": 32}
}
}
// ---- 不可逆算法 ----
func TestDecryptIrreversible(t *testing.T) {
irreversible := append([]encryptx.EncryptType{}, hashTypes...)
irreversible = append(irreversible, hmacTypes...)
irreversible = append(irreversible, kdfTypes...)
irreversible = append(irreversible, encryptx.BCRYPT)
for _, encryptType := range irreversible {
if _, err := encryptx.Decrypt(encryptType, "whatever", nil, nil); !errors.Is(err, encryptx.ErrIrreversible) {
t.Errorf("%s: 期望 ErrIrreversible, 得到 %v", encryptType, err)
}
}
}
func TestMissingKey(t *testing.T) {
for _, encryptType := range symmetricTypes {
if _, err := encryptx.Encrypt(encryptType, "x", nil, nil); !errors.Is(err, encryptx.ErrMissingKey) {
t.Errorf("%s: 期望 ErrMissingKey, 得到 %v", encryptType, err)
}
}
}
func TestGenerateKeyForKeylessAlgorithms(t *testing.T) {
for _, encryptType := range append(append([]encryptx.EncryptType{}, hashTypes...),
encryptx.BASE64, encryptx.BASE64_URL, encryptx.HEX, encryptx.BCRYPT) {
if _, err := encryptx.GenerateKey(encryptType, nil); err == nil {
t.Errorf("%s: 不应能生成密钥", encryptType)
}
}
}
// ---- 选项严格性 ----
// TestUnknownOptionRejected 是 P2-4 的回归测试。
//
// 历史版本用 map[string]interface{} 取参,键名拼错会被静默忽略并回退默认值,
// 导致「以为设置了参数、实际用的是默认值」这类安全隐患。现在必须报错。
func TestUnknownOptionRejected(t *testing.T) {
key, _ := encryptx.GenerateKey(encryptx.AES_CBC, nil)
if _, err := encryptx.Encrypt(encryptx.AES_CBC, "x", key,
encryptx.Options{"keyLen": 32}); !errors.Is(err, encryptx.ErrUnknownOption) {
t.Errorf("拼错的选项名: 期望 ErrUnknownOption, 得到 %v", err)
}
if _, err := encryptx.Encrypt(encryptx.PBKDF2, "x", nil,
encryptx.Options{"salt": bytes.Repeat([]byte{1}, 16), "iterration": 1000}); !errors.Is(err, encryptx.ErrUnknownOption) {
t.Errorf("拼错的 KDF 选项名: 期望 ErrUnknownOption, 得到 %v", err)
}
if _, err := encryptx.GenerateKey(encryptx.AES_CBC,
encryptx.Options{"keylength": 32}); !errors.Is(err, encryptx.ErrUnknownOption) {
t.Errorf("GenerateKey 未知选项: 期望 ErrUnknownOption, 得到 %v", err)
}
}
// TestOptionTypeMismatchRejected 验证类型不符时不再静默取默认值。
//
// 历史版本 `iterations, ok := options["iterations"].(int)` 在类型不符时 ok 为
// false,于是静默回退默认值——调用方以为设置了参数,实际用的却是默认值。
func TestOptionTypeMismatchRejected(t *testing.T) {
aesKey, err := encryptx.GenerateKey(encryptx.AES_CBC, nil)
2025-11-03 23:40:41 +08:00
if err != nil {
2026-09-30 15:49:12 +08:00
t.Fatalf("GenerateKey 失败: %v", err)
}
salt := bytes.Repeat([]byte{0x11}, 16)
cases := []struct {
name string
encryptType encryptx.EncryptType
key []byte
options encryptx.Options
}{
{"keyLength 传字符串", encryptx.PBKDF2, nil,
encryptx.Options{"salt": salt, "keyLength": "32"}},
{"keyLength 传结构体", encryptx.PBKDF2, nil,
encryptx.Options{"salt": salt, "keyLength": struct{}{}}},
{"keyLength 传布尔", encryptx.PBKDF2, nil,
encryptx.Options{"salt": salt, "keyLength": true}},
{"aad 传整数", encryptx.AES_GCM, aesKey, encryptx.Options{"aad": 123}},
{"iv 传整数", encryptx.AES_CBC, aesKey, encryptx.Options{"iv": 4242}},
{"salt 传整数", encryptx.PBKDF2, nil, encryptx.Options{"salt": 42}},
{"iterations 传字符串", encryptx.PBKDF2, nil,
encryptx.Options{"salt": salt, "iterations": "many"}},
{"aad 传布尔", encryptx.CHACHA20_POLY1305, aesKey, encryptx.Options{"aad": true}},
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
for _, c := range cases {
_, err := encryptx.Encrypt(c.encryptType, "payload", c.key, c.options)
if !errors.Is(err, encryptx.ErrInvalidOption) {
t.Errorf("%s: 期望 ErrInvalidOption, 得到 %v", c.name, err)
}
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// GenerateKey 同样不得静默回退
if _, err := encryptx.GenerateKey(encryptx.AES_CBC, encryptx.Options{"keyLength": "32"}); !errors.Is(err, encryptx.ErrInvalidOption) {
t.Errorf("GenerateKey 类型不符: 期望 ErrInvalidOption, 得到 %v", err)
}
}
// TestOptionsAcceptInt64 验证 JSON 解码常见的 int64 能被正确接受。
func TestOptionsAcceptInt64(t *testing.T) {
key, err := encryptx.GenerateKey(encryptx.AES_CBC, encryptx.Options{"keyLength": int64(16)})
2025-11-03 23:40:41 +08:00
if err != nil {
2026-09-30 15:49:12 +08:00
t.Fatalf("int64 形式的 keyLength 被拒绝: %v", err)
}
if len(key) != 16 {
t.Errorf("密钥长度 = %d, 期望 16", len(key))
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
key32, err := encryptx.GenerateKey(encryptx.AES_CBC, encryptx.Options{"keyLength": float64(32)})
2025-11-03 23:40:41 +08:00
if err != nil {
2026-09-30 15:49:12 +08:00
t.Fatalf("float64 形式的 keyLength 被拒绝: %v", err)
}
if len(key32) != 32 {
t.Errorf("密钥长度 = %d, 期望 32", len(key32))
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
if _, err := encryptx.GenerateKey(encryptx.AES_CBC, encryptx.Options{"keyLength": 16.5}); !errors.Is(err, encryptx.ErrInvalidOption) {
t.Errorf("非整数 float64: 期望 ErrInvalidOption, 得到 %v", err)
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
// ---- 口令哈希 ----
func TestPasswordHashing(t *testing.T) {
password := []byte("S3cr3t-P@ssw0rd")
cases := []struct {
encryptType encryptx.EncryptType
options encryptx.Options
prefix string
}{
{encryptx.BCRYPT, encryptx.Options{"cost": 4}, "$2"},
{encryptx.ARGON2ID, encryptx.Options{"time": 1, "memory": 8 * 1024, "threads": 1}, "$argon2id$"},
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
for _, c := range cases {
encoded, err := encryptx.HashPassword(c.encryptType, password, c.options)
if err != nil {
t.Fatalf("%s: HashPassword 失败: %v", c.encryptType, err)
}
if !strings.HasPrefix(encoded, c.prefix) {
t.Errorf("%s: 哈希串前缀 = %q, 期望以 %q 开头", c.encryptType, encoded, c.prefix)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
ok, err := encryptx.VerifyPassword(c.encryptType, password, encoded, nil)
if err != nil {
t.Fatalf("%s: VerifyPassword 返回错误: %v", c.encryptType, err)
}
if !ok {
t.Errorf("%s: 正确口令校验失败", c.encryptType)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
ok, err = encryptx.VerifyPassword(c.encryptType, []byte("wrong"), encoded, nil)
if err != nil {
t.Errorf("%s: 错误口令不应返回 error, 得到 %v", c.encryptType, err)
}
if ok {
t.Errorf("%s: 错误口令通过了校验", c.encryptType)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
// 非法哈希串应返回 error
if _, err := encryptx.VerifyPassword(c.encryptType, password, "not-a-hash", nil); err == nil {
t.Errorf("%s: 非法哈希串期望 error", c.encryptType)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
// Encrypt 也应支持口令哈希
viaEncrypt, err := encryptx.Encrypt(c.encryptType, string(password), nil, c.options)
if err != nil {
t.Fatalf("%s: Encrypt 形式的口令哈希失败: %v", c.encryptType, err)
}
if ok, _ := encryptx.VerifyPassword(c.encryptType, password, viaEncrypt, nil); !ok {
t.Errorf("%s: Encrypt 产生的哈希无法校验通过", c.encryptType)
}
2025-11-03 23:40:41 +08:00
2026-09-30 15:49:12 +08:00
// 同一口令两次哈希必须不同(随机盐)
second, _ := encryptx.HashPassword(c.encryptType, password, c.options)
if second == encoded {
t.Errorf("%s: 两次哈希结果相同, 随机盐可能未生效", c.encryptType)
}
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
// ---- 非对称加密与签名 ----
func TestRSARoundTrip(t *testing.T) {
privatePEM, err := encryptx.GenerateKey(encryptx.RSA, encryptx.Options{"bits": 2048})
if err != nil {
t.Fatalf("GenerateKey(RSA) 失败: %v", err)
}
priv, err := rsax.ParsePrivateKeyPEM(privatePEM)
if err != nil {
t.Fatalf("解析私钥失败: %v", err)
}
publicPEM, err := rsax.MarshalPublicKeyPEM(&priv.PublicKey)
if err != nil {
t.Fatalf("导出公钥失败: %v", err)
2025-11-03 23:40:41 +08:00
}
2026-09-30 15:49:12 +08:00
// GenerateKey 返回的是密钥材料本身,而不是像旧版本那样返回一句提示文本
if !bytes.Contains(privatePEM, []byte("PRIVATE KEY")) {
t.Error("GenerateKey(RSA) 未返回 PEM 密钥")
}
for _, plaintext := range []string{"short", strings.Repeat("long plaintext ", 400)} {
ciphertext, err := encryptx.Encrypt(encryptx.RSA, plaintext, publicPEM, nil)
if err != nil {
t.Fatalf("RSA 加密失败: %v", err)
}
decrypted, err := encryptx.Decrypt(encryptx.RSA, ciphertext, privatePEM, nil)
if err != nil {
t.Fatalf("RSA 解密失败: %v", err)
}
if decrypted != plaintext {
t.Errorf("RSA 往返不一致(明文长度 %d)", len(plaintext))
}
}
// 弱密钥位数必须被拒绝
if _, err := encryptx.GenerateKey(encryptx.RSA, encryptx.Options{"bits": 1024}); err == nil {
t.Error("1024 位 RSA 密钥应当被拒绝")
}
// 篡改密文必须失败
ciphertext, _ := encryptx.Encrypt(encryptx.RSA, "tamper me", publicPEM, nil)
tampered := []byte(ciphertext)
tampered[0] ^= 0x01
if _, err := encryptx.Decrypt(encryptx.RSA, string(tampered), privatePEM, nil); err == nil {
t.Error("篡改后的 RSA 密文竟然解密成功")
2025-11-03 23:40:41 +08:00
}
}
2026-09-30 15:49:12 +08:00
func TestRSAKeyFileWorkflow(t *testing.T) {
dir := t.TempDir()
privatePath := filepath.Join(dir, "private.pem")
publicPath := filepath.Join(dir, "public.pem")
_, err := encryptx.GenerateKey(encryptx.RSA, encryptx.Options{
"bits": 2048,
"privateKeyFile": privatePath,
"publicKeyFile": publicPath,
})
if err != nil {
t.Fatalf("生成并落盘 RSA 密钥失败: %v", err)
}
// 只给路径、不给密钥内容
ciphertext, err := encryptx.Encrypt(encryptx.RSA, "via files", nil,
encryptx.Options{"publicKeyFile": publicPath})
if err != nil {
t.Fatalf("按文件路径加密失败: %v", err)
}
decrypted, err := encryptx.Decrypt(encryptx.RSA, ciphertext, nil,
encryptx.Options{"privateKeyFile": privatePath})
if err != nil {
t.Fatalf("按文件路径解密失败: %v", err)
}
if decrypted != "via files" {
t.Errorf("往返不一致: %q", decrypted)
}
// 文件不存在必须返回 error,而不是像旧版本那样 panic
if _, err := encryptx.Encrypt(encryptx.RSA, "x", nil,
encryptx.Options{"publicKeyFile": filepath.Join(dir, "missing.pem")}); err == nil {
t.Error("公钥文件不存在时应当返回错误")
}
if runtime.GOOS != "windows" {
// 私钥文件权限应为 0600
info, err := os.Stat(privatePath)
if err != nil {
t.Fatalf("stat 私钥文件失败: %v", err)
}
if perm := info.Mode().Perm(); perm != 0o600 {
t.Errorf("私钥文件权限 = %o, 期望 600", perm)
}
}
}
// keyPairOptions 返回生成该签名算法密钥对所需的选项。
//
// 只有 RSA 有 bits 参数;Ed25519 与 ECDSA 的强度由算法/曲线决定,传入 bits
// 会被判为未知选项(这正是选项严格校验的体现)。
func keyPairOptions(signType encryptx.SignType) encryptx.Options {
if signType.UsesRSA() {
return encryptx.Options{"bits": 2048}
}
return nil
}
func TestSignAndVerify(t *testing.T) {
const message = "待签名的消息 with unicode 🔏"
for _, signType := range encryptx.GetAllSignTypes() {
privatePEM, publicPEM, err := encryptx.GenerateKeyPair(signType, keyPairOptions(signType))
if err != nil {
t.Fatalf("%s: GenerateKeyPair 失败: %v", signType, err)
}
signature, err := encryptx.Sign(signType, message, privatePEM, nil)
if err != nil {
t.Fatalf("%s: 签名失败: %v", signType, err)
}
if err := encryptx.Verify(signType, message, signature, publicPEM, nil); err != nil {
t.Errorf("%s: 正确签名验签失败: %v", signType, err)
}
// 篡改消息
if err := encryptx.Verify(signType, message+"!", signature, publicPEM, nil); err == nil {
t.Errorf("%s: 篡改消息后验签竟然通过", signType)
}
// 篡改签名
badSignature := []byte(signature)
badSignature[0] ^= 0x01
if err := encryptx.Verify(signType, message, string(badSignature), publicPEM, nil); err == nil {
t.Errorf("%s: 篡改签名后验签竟然通过", signType)
}
// 错误公钥
_, otherPublicPEM, err := encryptx.GenerateKeyPair(signType, keyPairOptions(signType))
if err != nil {
t.Fatalf("%s: %v", signType, err)
}
if err := encryptx.Verify(signType, message, signature, otherPublicPEM, nil); err == nil {
t.Errorf("%s: 用错误公钥验签竟然通过", signType)
}
// 非法的签名 Base64
if err := encryptx.Verify(signType, message, "!!!not-base64!!!", publicPEM, nil); err == nil {
t.Errorf("%s: 非法签名 Base64 期望错误", signType)
}
// 把加密用的私钥当公钥传入应当报错
if err := encryptx.Verify(signType, message, signature, privatePEM, nil); err == nil {
t.Errorf("%s: 用私钥验签竟然通过", signType)
}
}
}
func TestSignInvalidInputs(t *testing.T) {
bogus := encryptx.SignType(9999)
if bogus.Valid() {
t.Error("SignType(9999).Valid() = true")
}
if _, err := encryptx.Sign(bogus, "x", nil, nil); !errors.Is(err, encryptx.ErrUnsupportedType) {
t.Errorf("Sign: 期望 ErrUnsupportedType, 得到 %v", err)
}
if err := encryptx.Verify(bogus, "x", "sig", nil, nil); !errors.Is(err, encryptx.ErrUnsupportedType) {
t.Errorf("Verify: 期望 ErrUnsupportedType, 得到 %v", err)
}
// 缺少密钥
if _, err := encryptx.Sign(encryptx.ED25519, "x", nil, nil); !errors.Is(err, encryptx.ErrMissingKey) {
t.Errorf("Sign 缺少密钥: 期望 ErrMissingKey, 得到 %v", err)
}
if err := encryptx.Verify(encryptx.ED25519, "x", "sig", nil, nil); !errors.Is(err, encryptx.ErrMissingKey) {
t.Errorf("Verify 缺少密钥: 期望 ErrMissingKey, 得到 %v", err)
}
// 垃圾 PEM
if _, err := encryptx.Sign(encryptx.ED25519, "x", []byte("not a pem"), nil); err == nil {
t.Error("垃圾 PEM 期望错误")
}
if err := encryptx.Verify(encryptx.ED25519, "x", "c2ln", []byte("not a pem"), nil); err == nil {
t.Error("垃圾 PEM 期望错误")
}
// 算法与密钥类型不匹配:把 Ed25519 密钥当 RSA 用
privatePEM, _, err := encryptx.GenerateKeyPair(encryptx.ED25519, nil)
if err != nil {
t.Fatalf("GenerateKeyPair 失败: %v", err)
}
if _, err := encryptx.Sign(encryptx.RSA_PSS, "x", privatePEM, nil); err == nil {
t.Error("用 Ed25519 私钥做 RSA 签名应当报错")
}
}
func TestGenerateKeyPairECDSA(t *testing.T) {
// ECDSA 的曲线必须与 SignType 匹配
privatePEM, publicPEM, err := encryptx.GenerateKeyPair(encryptx.ECDSA_P256, nil)
if err != nil {
t.Fatalf("GenerateKeyPair(ECDSA_P256) 失败: %v", err)
}
signature, err := encryptx.Sign(encryptx.ECDSA_P256, "msg", privatePEM, nil)
if err != nil {
t.Fatalf("签名失败: %v", err)
}
if err := encryptx.Verify(encryptx.ECDSA_P256, "msg", signature, publicPEM, nil); err != nil {
t.Fatalf("验签失败: %v", err)
}
// 换一个曲线标识去验签,必须因曲线不匹配而失败
if err := encryptx.Verify(encryptx.ECDSA_P384, "msg", signature, publicPEM, nil); err == nil {
t.Error("曲线不匹配时验签应当失败")
}
}
// ---- 健壮性 ----
// TestDecryptGarbageNeverPanics 用大量畸形输入轰炸所有可逆算法。
//
// 历史版本在 ECB 解密时对非分组对齐的密文会触发
// "slice bounds out of range" panic,攻击者用一串伪造密文即可让服务崩溃。
func TestDecryptGarbageNeverPanics(t *testing.T) {
garbage := []string{
"", "0", "00", "zz", "!!!", "::::", "0:0",
strings.Repeat("A", 33), strings.Repeat("AB", 7), strings.Repeat("AB", 8),
strings.Repeat("AB", 9), strings.Repeat("ff", 100), "not-hex-at-all",
strings.Repeat("=", 10), "\n\n\n",
}
reversible := append([]encryptx.EncryptType{}, symmetricTypes...)
reversible = append(reversible, encryptx.BASE64, encryptx.BASE64_URL, encryptx.HEX)
for _, encryptType := range reversible {
var key []byte
if encryptType.NeedsKey() {
var err error
key, err = encryptx.GenerateKey(encryptType, nil)
if err != nil {
t.Fatalf("%s: GenerateKey 失败: %v", encryptType, err)
}
}
for i, input := range garbage {
// 不应 panic
_, _ = encryptx.Decrypt(encryptType, input, key, nil)
_ = i
}
}
}
func TestEncryptNeverPanicsOnNilOptions(t *testing.T) {
key, _ := encryptx.GenerateKey(encryptx.AES_CBC, nil)
if _, err := encryptx.Encrypt(encryptx.AES_CBC, "x", key, nil); err != nil {
t.Errorf("nil options 应当被接受, 得到 %v", err)
}
if _, err := encryptx.GenerateKey(encryptx.AES_CBC, nil); err != nil {
t.Errorf("nil options 应当被接受, 得到 %v", err)
}
}
// TestConcurrentUse 验证门面可被并发安全调用(无共享可变状态)。
func TestConcurrentUse(t *testing.T) {
key, err := encryptx.GenerateKey(encryptx.AES_GCM, nil)
if err != nil {
t.Fatalf("GenerateKey 失败: %v", err)
}
const goroutines = 16
done := make(chan error, goroutines)
for i := 0; i < goroutines; i++ {
go func(n int) {
plaintext := fmt.Sprintf("goroutine %d payload", n)
ciphertext, err := encryptx.Encrypt(encryptx.AES_GCM, plaintext, key, nil)
if err != nil {
done <- err
return
}
decrypted, err := encryptx.Decrypt(encryptx.AES_GCM, ciphertext, key, nil)
if err != nil {
done <- err
return
}
if decrypted != plaintext {
done <- fmt.Errorf("往返不一致: %q", decrypted)
return
}
done <- nil
}(i)
}
for i := 0; i < goroutines; i++ {
if err := <-done; err != nil {
t.Errorf("并发调用失败: %v", err)
}
2025-11-03 23:40:41 +08:00
}
}