完善不同加密方法的使用

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
+265 -10
View File
@@ -1,22 +1,277 @@
// Package hmacx 提供基于 crypto/hmac 的消息认证码(MAC)计算与校验能力。
//
// 设计要点与安全说明:
//
// 1. 本包所有计算函数都返回原始摘要字节([]byte),不返回可打印字符串,
// 以免调用方把密钥材料或摘要误写入日志。
// 2. 校验必须使用本包提供的 Verify* 函数,它们内部调用 crypto/hmac 的
// hmac.Equal 做常量时间比较;**绝不要**用 == 或 bytes.Equal 比较 MAC,
// 那会泄漏时序信息(timing side channel),使攻击者可以逐字节猜测正确 MAC。
// 3. 密钥长度短于哈希分组大小(SHA-1/SHA-256 为 64 字节,SHA-384/SHA-512 为
// 128 字节)时,HMAC 会先用 0x00 把密钥填充到分组长度,这是 RFC 2104 规定
// 的正常行为,不会降低安全性;安全性取决于密钥的**熵**而非长度。
// 但过短的密钥(少于 MinKeyLength 字节)会显著降低抗暴力破解能力,
// 应使用 crypto/rand 生成的随机密钥,长度建议为 DefaultKeyLength。
// 4. SHA-1 仅用于兼容既有协议(如 TLS 1.0、旧版 OAuth 1.0 签名)。
// SHA-1 的抗碰撞性已被攻破,新设计请使用 SHA-256 及以上。
package hmacx
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
)
// HMAC-SHA256 计算消息认证码
func HMACSHA256(message, key string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
const (
// MinKeyLength 是建议的最小 HMAC 密钥长度(字节)。
// 短于该长度的密钥熵不足,容易被暴力枚举,安全性显著下降。
MinKeyLength = 16
// DefaultKeyLength 是推荐的 HMAC 密钥长度(字节),
// 与 SHA-256 的摘要长度一致,可提供 256 位安全强度。
DefaultKeyLength = 32
)
// errNilReader 表示调用方传入了 nil 的 io.Reader。
var errNilReader = errors.New("reader 不能为 nil")
// Sum 使用给定的哈希构造函数计算 HMAC 摘要,返回原始摘要字节。
//
// 参数:
// - h: 哈希构造函数,例如 sha256.New;不能为 nil,否则返回 nil。
// - message: 待认证的消息,可以为空。
// - key: 认证密钥,可以为空(但空密钥等同于公开密钥,**没有认证意义**)。
//
// 安全注意:
// - 密钥应来自 crypto/rand,长度不小于 MinKeyLength。
// - 返回的摘要是敏感数据的完整性凭证,不要写进日志。
// - 计算本身不会失败,因此没有 error 返回值;h 为 nil 时返回 nil 以便安全降级。
func Sum(h func() hash.Hash, message, key []byte) []byte {
if h == nil {
return nil
}
mac := hmac.New(h, key)
_, _ = mac.Write(message)
return mac.Sum(nil)
}
// HMAC-SHA512 计算消息认证码
func HMACSHA512(message, key string) string {
mac := hmac.New(sha512.New, []byte(key))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
// Verify 使用给定的哈希构造函数校验 HMAC 摘要是否匹配。
//
// 参数:
// - h: 哈希构造函数,例如 sha256.New;为 nil 时直接返回 false。
// - message: 待认证的消息。
// - key: 认证密钥。
// - expectedMAC: 期望的摘要字节,长度必须等于 h() 的摘要长度。
//
// 返回值:仅在 expectedMAC 长度合法且常量时间比较相等时返回 true。
//
// 安全注意:
// - 内部使用 hmac.Equal(常量时间比较),不会泄漏时序信息。
// - expectedMAC 长度与摘要长度不符时立即返回 false,不会 panic。
// - 绝不能先用 == 比较 MAC 再调用本函数,那样时序漏洞依然存在。
func Verify(h func() hash.Hash, message, key, expectedMAC []byte) bool {
if h == nil {
return false
}
expectedLen := h().Size()
if len(expectedMAC) != expectedLen {
return false
}
mac := hmac.New(h, key)
_, _ = mac.Write(message)
return hmac.Equal(mac.Sum(nil), expectedMAC)
}
// SecureCompare 以常量时间比较两个字节串是否相等。
//
// 参数:
// - a, b: 待比较的字节串,长度不同时直接判定为不相等。
//
// 返回值:相等返回 true,否则返回 false。
//
// 安全注意:
// - 长度本身会通过返回结果与执行时间被区分出来,因此本函数只适用于比较
// 长度固定的秘密(如摘要、MAC);长度不固定的秘密应先做定长处理。
// - 底层使用 crypto/subtle.ConstantTimeCompare,比较耗时只与长度相关,
// 与内容无关,可抵御逐字节猜测攻击。
func SecureCompare(a, b []byte) bool {
return hmac.Equal(a, b)
}
// SumSHA1 计算 HMAC-SHA1 摘要(20 字节)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥,建议长度不小于 MinKeyLength。
//
// 安全注意:SHA-1 抗碰撞性已被攻破,仅用于兼容既有协议;
// 新设计请使用 SumSHA256 或 SumSHA512。密钥过短会降低抗暴力破解能力。
func SumSHA1(message, key []byte) []byte {
return Sum(sha1.New, message, key)
}
// SumSHA256 计算 HMAC-SHA256 摘要(32 字节),是推荐的默认选择。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥,建议长度不小于 MinKeyLength,推荐 DefaultKeyLength。
//
// 安全注意:密钥必须保密且具有足够熵;本函数不校验密钥长度,
// 过短密钥不会报错但会降低安全性,调用方应自行确保密钥质量。
func SumSHA256(message, key []byte) []byte {
return Sum(sha256.New, message, key)
}
// SumSHA384 计算 HMAC-SHA384 摘要(48 字节)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥,建议长度不小于 MinKeyLength。
//
// 安全注意:SHA-384 的分组大小为 128 字节,短于该长度的密钥会被 0x00 填充,
// 这是正常行为;密钥熵不足才是真正的风险。
func SumSHA384(message, key []byte) []byte {
return Sum(sha512.New384, message, key)
}
// SumSHA512 计算 HMAC-SHA512 摘要(64 字节)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥,建议长度不小于 MinKeyLength。
//
// 安全注意:摘要更长但计算开销也更高;多数场景 SHA-256 已足够。
func SumSHA512(message, key []byte) []byte {
return Sum(sha512.New, message, key)
}
// HexSHA1 计算 HMAC-SHA1 并返回小写十六进制字符串(40 个字符)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
//
// 安全注意:十六进制字符串便于传输与比较,但**校验时不要用 == 比较**,
// 请使用 VerifySHA1;另注意该字符串是完整性凭证,不宜写入日志。
func HexSHA1(message, key []byte) string {
return hex.EncodeToString(SumSHA1(message, key))
}
// HexSHA256 计算 HMAC-SHA256 并返回小写十六进制字符串(64 个字符)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
//
// 安全注意:**校验时不要用 == 比较**返回的字符串,请使用 VerifySHA256
// 以避免时序侧信道。
func HexSHA256(message, key []byte) string {
return hex.EncodeToString(SumSHA256(message, key))
}
// HexSHA384 计算 HMAC-SHA384 并返回小写十六进制字符串(96 个字符)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
//
// 安全注意:**校验时不要用 == 比较**返回的字符串,请使用 VerifySHA384。
func HexSHA384(message, key []byte) string {
return hex.EncodeToString(SumSHA384(message, key))
}
// HexSHA512 计算 HMAC-SHA512 并返回小写十六进制字符串(128 个字符)。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
//
// 安全注意:**校验时不要用 == 比较**返回的字符串,请使用 VerifySHA512。
func HexSHA512(message, key []byte) string {
return hex.EncodeToString(SumSHA512(message, key))
}
// SumReaderSHA256 从 io.Reader 流式读取消息并计算 HMAC-SHA256 摘要。
//
// 参数:
// - r: 消息来源,不能为 nil。
// - key: 认证密钥,建议长度不小于 MinKeyLength。
//
// 返回值:32 字节的原始摘要;读取失败时返回 nil 与包装后的 error。
//
// 安全注意:流式读取适合大消息,可避免整个消息驻留内存;
// 但 MAC 只保证完整性,不提供机密性,敏感数据仍需先加密。
func SumReaderSHA256(r io.Reader, key []byte) ([]byte, error) {
if r == nil {
return nil, fmt.Errorf("计算 HMAC-SHA256: %w", errNilReader)
}
mac := hmac.New(sha256.New, key)
if _, err := io.Copy(mac, r); err != nil {
return nil, fmt.Errorf("读取消息以计算 HMAC-SHA256: %w", err)
}
return mac.Sum(nil), nil
}
// VerifySHA1 校验 HMAC-SHA1 摘要是否匹配,使用常量时间比较。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
// - expectedMAC: 期望的 20 字节摘要。
//
// 返回值:长度合法且常量时间比较相等时返回 true,否则返回 false。
//
// 安全注意:expectedMAC 长度不是 20 时立即返回 false(不会 panic);
// 本函数不会因为比较失败而泄漏任何时序信息。
func VerifySHA1(message, key, expectedMAC []byte) bool {
return Verify(sha1.New, message, key, expectedMAC)
}
// VerifySHA256 校验 HMAC-SHA256 摘要是否匹配,使用常量时间比较。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
// - expectedMAC: 期望的 32 字节摘要。
//
// 返回值:长度合法且常量时间比较相等时返回 true,否则返回 false。
//
// 安全注意:expectedMAC 长度不是 32 时立即返回 false(不会 panic);
// 本函数是替代 == 比较的正确做法。
func VerifySHA256(message, key, expectedMAC []byte) bool {
return Verify(sha256.New, message, key, expectedMAC)
}
// VerifySHA384 校验 HMAC-SHA384 摘要是否匹配,使用常量时间比较。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
// - expectedMAC: 期望的 48 字节摘要。
//
// 返回值:长度合法且常量时间比较相等时返回 true,否则返回 false。
//
// 安全注意:expectedMAC 长度不是 48 时立即返回 false(不会 panic)。
func VerifySHA384(message, key, expectedMAC []byte) bool {
return Verify(sha512.New384, message, key, expectedMAC)
}
// VerifySHA512 校验 HMAC-SHA512 摘要是否匹配,使用常量时间比较。
//
// 参数:
// - message: 待认证的消息。
// - key: 认证密钥。
// - expectedMAC: 期望的 64 字节摘要。
//
// 返回值:长度合法且常量时间比较相等时返回 true,否则返回 false。
//
// 安全注意:expectedMAC 长度不是 64 时立即返回 false(不会 panic)。
func VerifySHA512(message, key, expectedMAC []byte) bool {
return Verify(sha512.New, message, key, expectedMAC)
}