完善不同加密方法的使用
This commit is contained in:
+265
-10
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user