完善不同加密方法的使用

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
+174 -44
View File
@@ -1,66 +1,196 @@
// Package pbkdf2x 基于 PBKDF2(RFC 8018 / PKCS #5 v2.1)提供口令密钥派生与校验。
//
// 安全注意事项:
// - PBKDF2 只有 CPU 代价、没有内存代价,抗 GPU/ASIC 暴力破解的能力弱于
// Argon2id 与 scrypt。新系统的口令存储应优先选用 argon2x,其次 scryptx;
// 本包主要用于兼容既有系统与 FIPS 场景。
// - 迭代次数必须随硬件能力上调。OWASP 2023 对 PBKDF2-HMAC-SHA256 的推荐值为
// 600000,即 DefaultIterations;低于 MinIterations 的配置会被拒绝。
// - 盐必须是密码学安全随机数、每个口令独立,长度不低于 MinSaltLength 字节,
// 推荐 DefaultSaltLength 字节。盐不是秘密,可与派生结果一起明文存储。
// - 派生密钥属于敏感数据,调用方负责及时清零,且不得写入日志或错误信息。
//
// 本包任何情况下都不会 panic:所有非法参数都返回 error,且错误文本中不含
// 口令、盐或密钥材料。
package pbkdf2x
import (
"crypto/pbkdf2"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"crypto/subtle"
"fmt"
"golang.org/x/crypto/pbkdf2"
"hash"
"io"
)
// GenerateSalt 生成随机盐值
func GenerateSalt(length int) (string, error) {
if length <= 0 {
return "", fmt.Errorf("盐值长度必须大于0")
const (
// DefaultIterations 是 PBKDF2-HMAC-SHA256 的默认迭代次数,取 OWASP 2023 年
// 口令存储建议值 600000。迭代次数越高,离线暴力破解成本越高,派生耗时
// 也近似线性增长。
DefaultIterations = 600000
// MinIterations 是允许的最小迭代次数。1 万次迭代对现代 GPU 几乎不构成
// 防护,低于该值的请求一律拒绝,避免调用方误传 1 或 1000。
MinIterations = 10000
// MaxIterations 是允许的最大迭代次数,用于拦截误把毫秒、字节数当成
// 迭代次数传入的调用(例如 0xFFFFFFFF),避免单次请求长时间占用 CPU
// 而演变成拒绝服务放大面。
MaxIterations = 10000000
// DefaultSaltLength 是推荐的盐长度(字节)。盐越长,彩虹表与多目标
// 批量攻击的收益越低。
DefaultSaltLength = 16
// MinSaltLength 是允许的最小盐长度(字节)。RFC 8018 建议至少 8 字节,
// 更短的盐会显著提高碰撞概率。
MinSaltLength = 8
// DefaultKeyLength 是推荐的派生密钥长度(字节),32 字节可直接作为
// AES-256 或 HMAC-SHA256 的密钥。
DefaultKeyLength = 32
// MaxKeyLength 是允许的最大派生密钥长度(字节)。该上限用于防止调用方
// 把"输出缓冲区大小"误传入,造成不必要的内存分配与计算开销。
MaxKeyLength = 1024
)
// maxSaltLength 是允许的最大盐长度(字节)。盐不是秘密,超长盐没有额外收益,
// 这里设上限只是为了让明显误传的参数(如 1<<20)尽早失败。
const maxSaltLength = 1024
// GenerateSalt 返回 length 字节的密码学安全随机盐,供 DeriveKey 使用。
//
// length 必须落在 [MinSaltLength, 1024] 区间内,推荐传 DefaultSaltLength。
// 每次注册、改密或加密新数据都必须生成新的盐,并把盐与派生结果一起持久化:
// 盐是公开值,缺失它会导致派生结果永远无法复现。
//
// 随机源不可用时返回 error,绝不退化为可预测的盐。
func GenerateSalt(length int) ([]byte, error) {
if length < MinSaltLength {
return nil, fmt.Errorf("pbkdf2x: 盐长度 %d 小于最小值 %d", length, MinSaltLength)
}
if length > maxSaltLength {
return nil, fmt.Errorf("pbkdf2x: 盐长度 %d 超过最大值 %d", length, maxSaltLength)
}
salt := make([]byte, length)
_, err := rand.Read(salt)
if err != nil {
return "", fmt.Errorf("生成盐值失败: %v", err)
// 使用 io.ReadFull(rand.Reader) 而不是 rand.Read:后者在随机源故障时
// 会直接终止进程,而本包要求把失败作为 error 返回给调用方。
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, fmt.Errorf("pbkdf2x: 读取随机盐失败: %w", err)
}
return hex.EncodeToString(salt), nil
return salt, nil
}
// DeriveKeyFromPassword 使用PBKDF2从密码派生密钥
// password: 原始密码
// saltHex: 十六进制格式的盐值
// iterations: 迭代次数,推荐至少10000次
// keyLength: 派生密钥的长度(字节)
func DeriveKeyFromPassword(password, saltHex string, iterations, keyLength int) (string, error) {
// 解码盐值
salt, err := hex.DecodeString(saltHex)
if err != nil {
return "", fmt.Errorf("盐值解码失败: %v", err)
}
// 使用PBKDF2派生密钥
key := pbkdf2.Key([]byte(password), salt, iterations, keyLength, sha256.New)
// 返回十六进制格式的密钥
return hex.EncodeToString(key), nil
// DeriveKey 使用 PBKDF2-HMAC-SHA256 从 password 与 salt 派生 keyLength 字节密钥。
//
// password 为原始口令字节(不是十六进制串);salt 为 GenerateSalt 产生的随机盐
// 或调用方自行管理的唯一盐。iterations 需落在 [MinIterations, MaxIterations],
// keyLength 需落在 [1, MaxKeyLength],推荐 DefaultIterations 与 DefaultKeyLength。
//
// 相同的 (password, salt, iterations, keyLength) 必然得到相同密钥;只要盐变化,
// 结果即完全不同。因此派生结果必须连同盐与参数一起保存才能复现。
//
// 安全提示:派生结果不可逆推口令,但如果口令熵不足,攻击者仍可离线穷举,
// 请配合口令强度策略与足够的迭代次数使用。
func DeriveKey(password, salt []byte, iterations, keyLength int) ([]byte, error) {
return DeriveKeyWithHash(password, salt, iterations, keyLength, sha256.New)
}
// DeriveKeyFromPasswordWithRandomSalt 使用PBKDF2从密码派生密钥,并自动生成随机盐值
// password: 原始密码
// saltLength: 盐值长度(字节)
// iterations: 迭代次数,推荐至少10000次
// keyLength: 派生密钥的长度(字节)
// 返回值: 密钥(十六进制), 盐值(十六进制), 错误
func DeriveKeyFromPasswordWithRandomSalt(password string, saltLength, iterations, keyLength int) (string, string, error) {
// 生成随机盐值
saltHex, err := GenerateSalt(saltLength)
if err != nil {
return "", "", err
// DeriveKeyWithHash 与 DeriveKey 相同,但允许指定 PBKDF2 的伪随机函数底层哈希,
// 例如 sha1.New(仅用于兼容旧系统)、sha512.New。h 为 nil 时返回 error。
//
// 参数校验与 DeriveKey 完全一致:盐长度、迭代次数、输出长度越界都会返回 error。
// 安全提示:SHA-1/MD5 已被证明存在碰撞攻击,新系统不要使用;更换哈希属于不兼容
// 变更,同一份存储数据必须固定使用创建时所用的哈希。
func DeriveKeyWithHash(password, salt []byte, iterations, keyLength int, h func() hash.Hash) ([]byte, error) {
if err := validateParams(salt, iterations, keyLength); err != nil {
return nil, err
}
if h == nil {
return nil, fmt.Errorf("pbkdf2x: 哈希构造函数不能为 nil")
}
// 派生密钥
keyHex, err := DeriveKeyFromPassword(password, saltHex, iterations, keyLength)
// 使用标准库 crypto/pbkdf2:它返回 error;而 golang.org/x/crypto/pbkdf2.Key
// 在参数非法或 FIPS 140-only 模式下会 panic,与本包"绝不 panic"的约定冲突,
// 且 FIPS 限制无法通过入口校验完全规避。
key, err := pbkdf2.Key(h, string(password), salt, iterations, keyLength)
if err != nil {
return "", "", err
return nil, fmt.Errorf("pbkdf2x: 派生密钥失败: %w", err)
}
return key, nil
}
return keyHex, saltHex, nil
// DeriveKeyWithRandomSalt 生成 saltLength 字节随机盐并立即派生密钥,一次性返回
// (key, salt, error)。这是推荐入口:调用方必须把返回的 salt 与 key 一起保存
// (例如 salt 存明文列、key 作为校验值),否则无法复现派生结果。
//
// saltLength 通常传 DefaultSaltLength;iterations 与 keyLength 的含义同 DeriveKey。
// 任何参数非法或随机源失败都会返回 error,此时 key 与 salt 均为 nil。
func DeriveKeyWithRandomSalt(password []byte, saltLength, iterations, keyLength int) (key, salt []byte, err error) {
salt, err = GenerateSalt(saltLength)
if err != nil {
return nil, nil, err
}
key, err = DeriveKey(password, salt, iterations, keyLength)
if err != nil {
return nil, nil, err
}
return key, salt, nil
}
// Verify 用相同参数重新派生密钥,并以常量时间比较 expectedKey,返回是否匹配。
//
// 参数非法(盐过短、迭代次数越界、keyLength 越界)时返回 error;参数合法但密钥
// 不匹配时返回 (false, nil)。expectedKey 长度与 keyLength 不一致时直接判定为
// 不匹配,不会泄漏比较进度。
//
// 安全提示:必须使用本函数而不是 bytes.Equal 或 == 比较密钥,后者会因提前返回
// 而泄漏前缀匹配长度,构成时序侧信道。
func Verify(password, salt, expectedKey []byte, iterations, keyLength int) (bool, error) {
derived, err := DeriveKey(password, salt, iterations, keyLength)
if err != nil {
return false, err
}
// 比较完成后立即清零本地派生结果,缩短密钥在内存中的驻留时间。
defer func() {
for i := range derived {
derived[i] = 0
}
}()
if len(expectedKey) != len(derived) {
return false, nil
}
return subtle.ConstantTimeCompare(derived, expectedKey) == 1, nil
}
// validateParams 是所有派生入口共用的参数闸门,保证非法输入在进入底层实现
// 之前就变成明确的 error,而不是 panic 或静默截断。
func validateParams(salt []byte, iterations, keyLength int) error {
if len(salt) == 0 {
return fmt.Errorf("pbkdf2x: 盐不能为空")
}
if len(salt) < MinSaltLength {
return fmt.Errorf("pbkdf2x: 盐长度 %d 小于最小值 %d", len(salt), MinSaltLength)
}
if len(salt) > maxSaltLength {
return fmt.Errorf("pbkdf2x: 盐长度 %d 超过最大值 %d", len(salt), maxSaltLength)
}
if iterations < MinIterations {
return fmt.Errorf("pbkdf2x: 迭代次数 %d 小于最小值 %d", iterations, MinIterations)
}
if iterations > MaxIterations {
return fmt.Errorf("pbkdf2x: 迭代次数 %d 超过最大值 %d", iterations, MaxIterations)
}
if keyLength <= 0 {
return fmt.Errorf("pbkdf2x: 密钥长度 %d 必须大于 0", keyLength)
}
if keyLength > MaxKeyLength {
return fmt.Errorf("pbkdf2x: 密钥长度 %d 超过最大值 %d", keyLength, MaxKeyLength)
}
return nil
}
+345
View File
@@ -0,0 +1,345 @@
package pbkdf2x_test
import (
"bytes"
"crypto/sha1"
"crypto/sha512"
"encoding/hex"
"math"
"testing"
"code.yun.ink/pkg/encryptx/pbkdf2x"
)
// mustHexDecode 把测试向量里的十六进制串还原为字节。
func mustHexDecode(t *testing.T, s string) []byte {
t.Helper()
b, err := hex.DecodeString(s)
if err != nil {
t.Fatalf("测试向量不是合法十六进制: %v", err)
}
return b
}
// TestDeriveKeyKnownVectors 固定本包的已知答案(KAT)。
// 期望值由 Python 3.13 的 hashlib.pbkdf2_hmac(独立实现)生成,例如:
//
// hashlib.pbkdf2_hmac('sha256', b'password', b'saltsaltsalt', 10000, 32).hex()
func TestDeriveKeyKnownVectors(t *testing.T) {
cases := []struct {
name string
password []byte
saltHex string
iterations int
keyLength int
want string
}{
{
name: "sha256-10000-32",
password: []byte("password"),
saltHex: hex.EncodeToString([]byte("saltsaltsalt")),
iterations: 10000,
keyLength: 32,
want: "63086a6bd6ba533c492ed8d8453f99d723a9fbc2c427c2b848784308fc88cad3",
},
{
name: "sha256-10000-64",
password: []byte("password"),
saltHex: hex.EncodeToString([]byte("saltsaltsalt")),
iterations: 10000,
keyLength: 64,
want: "63086a6bd6ba533c492ed8d8453f99d723a9fbc2c427c2b848784308fc88cad33b33c8dad2c6f11785d281ae729f83877daa2b8afe1923791c46a8e226f92f4a",
},
{
// 含 0x00 的二进制盐:验证盐是原始字节而不是十六进制字符串。
name: "sha256-100000-32-binary-salt",
password: []byte("Correct Horse Battery Staple"),
saltHex: "000102030405060708090a0b0c0d0e0f",
iterations: 100000,
keyLength: 32,
want: "26707b0d34414adebe877b7345529a17095c7b56bdea133de943583d1e5e2efd",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := pbkdf2x.DeriveKey(tc.password, mustHexDecode(t, tc.saltHex), tc.iterations, tc.keyLength)
if err != nil {
t.Fatalf("DeriveKey 返回错误: %v", err)
}
if hex.EncodeToString(got) != tc.want {
t.Errorf("派生结果与已知向量不一致\n got: %s\nwant: %s", hex.EncodeToString(got), tc.want)
}
})
}
}
// TestDeriveKeyWithHashKnownVector 验证 PRF 可替换:期望值同样由 Python 的
// hashlib.pbkdf2_hmac('sha512', ...) 独立生成。
func TestDeriveKeyWithHashKnownVector(t *testing.T) {
const want = "c3d64081b5d8986fe81498e5cf0f5ed0c3bf9f011ac699ead49a7b192fe69ef239f970ba2de9827289e036f9d80fc18cc870c518d16f512cb623017df08820f8"
got, err := pbkdf2x.DeriveKeyWithHash([]byte("password"), []byte("saltsaltsalt"), 10000, 64, sha512.New)
if err != nil {
t.Fatalf("DeriveKeyWithHash 返回错误: %v", err)
}
if hex.EncodeToString(got) != want {
t.Errorf("PBKDF2-HMAC-SHA512 结果与已知向量不一致\n got: %s\nwant: %s", hex.EncodeToString(got), want)
}
// 换哈希必须得到不同结果,证明 h 真正生效而不是被写死为 SHA-256。
sha256Key, err := pbkdf2x.DeriveKey([]byte("password"), []byte("saltsaltsalt"), 10000, 64)
if err != nil {
t.Fatalf("DeriveKey 返回错误: %v", err)
}
if bytes.Equal(sha256Key, got) {
t.Error("SHA-512 与 SHA-256 派生结果相同,说明哈希参数未生效")
}
// SHA-1 仅用于兼容旧系统,这里只验证可用性与长度。
sha1Key, err := pbkdf2x.DeriveKeyWithHash([]byte("password"), []byte("saltsaltsalt"), 10000, 20, sha1.New)
if err != nil {
t.Fatalf("DeriveKeyWithHash(sha1) 返回错误: %v", err)
}
if len(sha1Key) != 20 {
t.Errorf("SHA-1 派生长度 = %d, 期望 20", len(sha1Key))
}
}
// TestDeriveKeyReproducible 覆盖最核心的语义:同口令同盐同参数必须可复现。
func TestDeriveKeyReproducible(t *testing.T) {
password := []byte("正确的马电池订书钉")
salt := []byte("0123456789abcdef")
k1, err := pbkdf2x.DeriveKey(password, salt, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("第一次派生失败: %v", err)
}
k2, err := pbkdf2x.DeriveKey(password, salt, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("第二次派生失败: %v", err)
}
if !bytes.Equal(k1, k2) {
t.Fatal("相同输入两次派生结果不一致,无法复现")
}
if len(k1) != pbkdf2x.DefaultKeyLength {
t.Fatalf("派生长度 = %d, 期望 %d", len(k1), pbkdf2x.DefaultKeyLength)
}
otherSalt, err := pbkdf2x.DeriveKey(password, []byte("fedcba9876543210"), pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("换盐派生失败: %v", err)
}
if bytes.Equal(k1, otherSalt) {
t.Error("不同盐得到了相同密钥")
}
otherPassword, err := pbkdf2x.DeriveKey([]byte("错误口令"), salt, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("换口令派生失败: %v", err)
}
if bytes.Equal(k1, otherPassword) {
t.Error("不同口令得到了相同密钥")
}
otherIterations, err := pbkdf2x.DeriveKey(password, salt, pbkdf2x.MinIterations*2, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("换迭代次数派生失败: %v", err)
}
if bytes.Equal(k1, otherIterations) {
t.Error("不同迭代次数得到了相同密钥")
}
}
// TestDeriveKeyWithRandomSalt 针对被修复的核心缺陷:随机盐必须一并返回,
// 且该盐必须能复现同一个密钥。
func TestDeriveKeyWithRandomSalt(t *testing.T) {
password := []byte("hunter2")
key, salt, err := pbkdf2x.DeriveKeyWithRandomSalt(password, pbkdf2x.DefaultSaltLength, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("DeriveKeyWithRandomSalt 返回错误: %v", err)
}
if len(salt) != pbkdf2x.DefaultSaltLength {
t.Fatalf("返回的盐长度 = %d, 期望 %d(盐必须是原始字节,不是十六进制字符串)", len(salt), pbkdf2x.DefaultSaltLength)
}
if len(key) != pbkdf2x.DefaultKeyLength {
t.Fatalf("返回的密钥长度 = %d, 期望 %d", len(key), pbkdf2x.DefaultKeyLength)
}
// 用返回的盐复现:这是旧实现丢失盐值后无法做到的事情。
reproduced, err := pbkdf2x.DeriveKey(password, salt, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("用返回的盐复现失败: %v", err)
}
if !bytes.Equal(key, reproduced) {
t.Fatal("用 DeriveKeyWithRandomSalt 返回的盐无法复现同一密钥")
}
key2, salt2, err := pbkdf2x.DeriveKeyWithRandomSalt(password, pbkdf2x.DefaultSaltLength, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("第二次 DeriveKeyWithRandomSalt 失败: %v", err)
}
if bytes.Equal(salt, salt2) {
t.Error("两次调用返回了相同的盐,随机盐未生效")
}
if bytes.Equal(key, key2) {
t.Error("两次调用返回了相同的密钥")
}
// 非法 saltLength 必须报错而不是 panic。
for _, bad := range []int{0, -1, pbkdf2x.MinSaltLength - 1, 1025, math.MaxInt} {
if _, _, err := pbkdf2x.DeriveKeyWithRandomSalt(password, bad, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength); err == nil {
t.Errorf("saltLength=%d 时未返回错误", bad)
}
}
}
func TestVerify(t *testing.T) {
password := []byte("s3cret-口令")
salt := []byte("0123456789abcdef")
key, err := pbkdf2x.DeriveKey(password, salt, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("派生失败: %v", err)
}
ok, err := pbkdf2x.Verify(password, salt, key, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("Verify 返回错误: %v", err)
}
if !ok {
t.Error("正确口令 + 正确盐未通过校验")
}
cases := []struct {
name string
password []byte
salt []byte
expected []byte
}{
{"错误口令", []byte("s3cret-口令x"), salt, key},
{"错误盐", password, []byte("fedcba9876543210"), key},
{"密钥被篡改", password, salt, append(append([]byte{}, key[:len(key)-1]...), key[len(key)-1]^0x01)},
{"密钥被截断", password, salt, key[:len(key)-1]},
{"密钥被加长", password, salt, append(append([]byte{}, key...), 0x00)},
{"空密钥", password, salt, nil},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ok, err := pbkdf2x.Verify(tc.password, tc.salt, tc.expected, pbkdf2x.MinIterations, pbkdf2x.DefaultKeyLength)
if err != nil {
t.Fatalf("参数合法时 Verify 不应返回错误: %v", err)
}
if ok {
t.Error("不匹配的密钥被判为匹配")
}
})
}
}
// TestParameterValidation 覆盖所有非法参数路径:必须返回 error,且绝不 panic。
func TestParameterValidation(t *testing.T) {
validSalt := bytes.Repeat([]byte{0xA5}, pbkdf2x.MinSaltLength)
password := []byte("pw")
cases := []struct {
name string
salt []byte
iterations int
keyLength int
saltInvalid bool
}{
{"盐为 nil", nil, pbkdf2x.MinIterations, 32, true},
{"盐为空切片", []byte{}, pbkdf2x.MinIterations, 32, true},
{"盐过短", []byte("1234567"), pbkdf2x.MinIterations, 32, true},
{"盐过长", bytes.Repeat([]byte{0x01}, 1025), pbkdf2x.MinIterations, 32, true},
{"迭代次数为 0", validSalt, 0, 32, false},
{"迭代次数为负", validSalt, -1, 32, false},
{"迭代次数低于下限", validSalt, pbkdf2x.MinIterations - 1, 32, false},
{"迭代次数超上限", validSalt, pbkdf2x.MaxIterations + 1, 32, false},
{"迭代次数为 MaxInt", validSalt, math.MaxInt, 32, false},
{"密钥长度为 0", validSalt, pbkdf2x.MinIterations, 0, false},
{"密钥长度为负", validSalt, pbkdf2x.MinIterations, -32, false},
{"密钥长度超上限", validSalt, pbkdf2x.MinIterations, pbkdf2x.MaxKeyLength + 1, false},
{"密钥长度为 MaxInt", validSalt, pbkdf2x.MinIterations, math.MaxInt, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
key, err := pbkdf2x.DeriveKey(password, tc.salt, tc.iterations, tc.keyLength)
if err == nil {
t.Fatalf("DeriveKey 未返回错误,得到 %d 字节密钥", len(key))
}
if key != nil {
t.Errorf("出错时密钥应为 nil,实际长度 %d", len(key))
}
if _, err := pbkdf2x.Verify(password, tc.salt, validSalt, tc.iterations, tc.keyLength); err == nil {
t.Error("Verify 对非法参数未返回错误")
}
if _, err := pbkdf2x.DeriveKeyWithHash(password, tc.salt, tc.iterations, tc.keyLength, nil); err == nil {
t.Error("DeriveKeyWithHash 对非法参数未返回错误")
}
// DeriveKeyWithRandomSalt 自行生成盐,因此仅在迭代次数/长度非法时才应报错。
if !tc.saltInvalid {
if _, _, err := pbkdf2x.DeriveKeyWithRandomSalt(password, pbkdf2x.DefaultSaltLength, tc.iterations, tc.keyLength); err == nil {
t.Error("DeriveKeyWithRandomSalt 对非法参数未返回错误")
}
}
})
}
// 合法的盐/迭代次数/长度组合但 h 为 nil:同样必须是 error 而不是 panic。
if _, err := pbkdf2x.DeriveKeyWithHash(password, validSalt, pbkdf2x.MinIterations, 32, nil); err == nil {
t.Error("哈希构造函数为 nil 时未返回错误")
}
}
// TestBoundaryValues 验证边界值落在"允许"一侧时确实可用。
func TestBoundaryValues(t *testing.T) {
salt := bytes.Repeat([]byte{0x5A}, pbkdf2x.MinSaltLength)
// 最小迭代次数 + 最大密钥长度。
key, err := pbkdf2x.DeriveKey([]byte("pw"), salt, pbkdf2x.MinIterations, pbkdf2x.MaxKeyLength)
if err != nil {
t.Fatalf("边界参数派生失败: %v", err)
}
if len(key) != pbkdf2x.MaxKeyLength {
t.Errorf("密钥长度 = %d, 期望 %d", len(key), pbkdf2x.MaxKeyLength)
}
// 最小密钥长度。
if _, err := pbkdf2x.DeriveKey([]byte("pw"), salt, pbkdf2x.MinIterations, 1); err != nil {
t.Errorf("keyLength=1 应被接受: %v", err)
}
}
func TestGenerateSalt(t *testing.T) {
for _, length := range []int{pbkdf2x.MinSaltLength, pbkdf2x.DefaultSaltLength, 32, 64} {
salt, err := pbkdf2x.GenerateSalt(length)
if err != nil {
t.Fatalf("GenerateSalt(%d) 返回错误: %v", length, err)
}
if len(salt) != length {
t.Errorf("盐长度 = %d, 期望 %d", len(salt), length)
}
}
a, err := pbkdf2x.GenerateSalt(pbkdf2x.DefaultSaltLength)
if err != nil {
t.Fatalf("GenerateSalt 返回错误: %v", err)
}
b, err := pbkdf2x.GenerateSalt(pbkdf2x.DefaultSaltLength)
if err != nil {
t.Fatalf("GenerateSalt 返回错误: %v", err)
}
if bytes.Equal(a, b) {
t.Error("两次生成的盐相同,随机性有问题")
}
for _, length := range []int{0, -1, pbkdf2x.MinSaltLength - 1, 1025, math.MaxInt} {
if _, err := pbkdf2x.GenerateSalt(length); err == nil {
t.Errorf("GenerateSalt(%d) 未返回错误", length)
}
}
}