完善不同加密方法的使用

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
+85
View File
@@ -0,0 +1,85 @@
// Package hkdfx 基于 HKDF(RFC 5869)实现 "extract-then-expand" 密钥派生。
//
// 适用场景:把已有的高熵密钥材料(ECDH/X25519 共享密钥、KMS 下发的随机
// master key、随机生成的根密钥)扩展成多个用途不同的子密钥,或用 info 做
// 密钥分离。
//
// 安全注意事项:
// - HKDF 不是慢哈希,没有任何迭代与内存硬化环节,绝对不能用于低熵口令。
// 用 HKDF 处理用户口令等于把口令直接当密钥,攻击者可离线高速穷举;
// 口令请使用 argon2x(首选)或 scryptx。
// - secret 必须是非空的高熵密钥材料;空材料被拒绝。
// - salt 可以为 nil:此时按 RFC 5869 使用 HashLen 个零字节作为盐。salt 不必
// 保密,但应当唯一,用于隔离不同会话/不同对端。
// - info 是公开的上下文标签(如 "aes-key" / "hmac-key")。用同一份 secret
// 派生多个子密钥时必须给出互不相同的 info,否则会得到相同的密钥。
// - 一次派生的长度不能超过 255*HashLen 字节(HKDF-SHA256 即 MaxKeyLength),
// 超过该上限在密码学上无意义,本包直接拒绝。
package hkdfx
import (
"crypto/sha256"
"fmt"
"hash"
"io"
"golang.org/x/crypto/hkdf"
)
const (
// DefaultKeyLength 是推荐的派生长度(字节),32 字节可直接作为 AES-256
// 或 HMAC-SHA256 的密钥。
DefaultKeyLength = 32
// MaxKeyLength 是 HKDF-SHA256 单次可派生的最大长度(字节),等于
// 255 * SHA-256 输出长度 32。使用其他哈希时真实上限为 255*HashLen,
// DeriveKeyWithHash 会按传入哈希的实际输出长度校验。
MaxKeyLength = 8160
)
// DeriveKey 使用 HKDF-SHA256 从高熵 secret 派生 keyLength 字节密钥。
//
// secret 为输入密钥材料,必须非空;salt 可以为 nil(按 RFC 5869 使用全零盐);
// info 是用途/上下文标签,可以为 nil。keyLength 必须落在 [1, MaxKeyLength]。
//
// 派生结果是 secret、salt、info 三者的确定性函数:三者相同则结果相同,任一不同
// 则结果不同,因此调用方只要保存 salt 与 info(它们是公开值)即可复现。
//
// 再次强调:不要用本函数处理用户口令,请改用 argon2x/scryptx。
func DeriveKey(secret, salt, info []byte, keyLength int) ([]byte, error) {
return DeriveKeyWithHash(secret, salt, info, keyLength, sha256.New)
}
// DeriveKeyWithHash 与 DeriveKey 相同,但允许指定底层 HMAC 哈希(如 sha256.New、
// sha512.New、sha1.New)。h 为 nil 时返回 error。
//
// 输出上限按 255*h().Size() 计算:SHA-256 为 8160 字节,SHA-512 为 16320 字节。
// 安全提示:更换哈希会派生完全不同的密钥,属于不兼容变更;SHA-1 仅用于兼容旧协议。
func DeriveKeyWithHash(secret, salt, info []byte, keyLength int, h func() hash.Hash) ([]byte, error) {
if len(secret) == 0 {
return nil, fmt.Errorf("hkdfx: 输入密钥材料 secret 不能为空")
}
if h == nil {
return nil, fmt.Errorf("hkdfx: 哈希构造函数不能为 nil")
}
if keyLength <= 0 {
return nil, fmt.Errorf("hkdfx: 派生长度 %d 必须大于 0", keyLength)
}
hashSize := h().Size()
if hashSize <= 0 {
return nil, fmt.Errorf("hkdfx: 哈希输出长度非法: %d", hashSize)
}
// RFC 5869 的 L 上限为 255*HashLen;超出时底层 Reader 会返回
// "hkdf: entropy limit reached",这里提前给出更明确的错误。
if maxLen := 255 * hashSize; keyLength > maxLen {
return nil, fmt.Errorf("hkdfx: 派生长度 %d 超过该哈希的上限 %d (%d*%d)", keyLength, maxLen, 255, hashSize)
}
out := make([]byte, keyLength)
// hkdf.New 内部执行 Extract + Expand;salt 为 nil 时按标准使用全零盐。
if _, err := io.ReadFull(hkdf.New(h, secret, salt, info), out); err != nil {
return nil, fmt.Errorf("hkdfx: 派生密钥失败: %w", err)
}
return out, nil
}