完善不同加密方法的使用

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)
}
+392 -54
View File
@@ -1,75 +1,413 @@
package hmacx
package hmacx_test
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"hash"
"strings"
"testing"
"code.yun.ink/pkg/encryptx/hmacx"
)
func TestHMACSHA256(t *testing.T) {
message := "hello world"
key := "secret_key"
// rfc4231Case 表示 RFC 4231 中的一个测试用例。
// 来源:RFC 4231 "Identifiers and Test Vectors for HMAC-SHA-224, HMAC-SHA-256,
// HMAC-SHA-384, and HMAC-SHA-512"(https://www.rfc-editor.org/rfc/rfc4231.txt)。
type rfc4231Case struct {
name string
key []byte
data []byte
sha256 string
sha384 string
sha512 string
}
// 计算HMAC-SHA256
hmac1 := HMACSHA256(message, key)
// rfc4231Cases 为 RFC 4231 第 4.2~4.5 节(Test Case 1~4)的 SHA-256/384/512 摘要,
// 以及 RFC 2202 Test Case 1~4 的 HMAC-SHA1 摘要(两组定义的 key/data 相同)。
var rfc4231Cases = []rfc4231Case{
{
name: "RFC4231-TestCase1",
key: bytes.Repeat([]byte{0x0b}, 20),
data: []byte("Hi There"),
sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
sha384: "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
sha512: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
},
{
name: "RFC4231-TestCase2",
key: []byte("Jefe"),
data: []byte("what do ya want for nothing?"),
sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
sha384: "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649",
sha512: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
},
{
name: "RFC4231-TestCase3",
key: bytes.Repeat([]byte{0xaa}, 20),
data: bytes.Repeat([]byte{0xdd}, 50),
sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
sha384: "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27",
sha512: "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
},
{
name: "RFC4231-TestCase4",
key: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19},
data: bytes.Repeat([]byte{0xcd}, 50),
sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
sha384: "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb",
sha512: "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd",
},
}
// 再次计算相同的消息和密钥
hmac2 := HMACSHA256(message, key)
// rfc2202SHA1Cases 为 RFC 2202(HMAC-MD5/HMAC-SHA-1 测试向量)Test Case 1~4 的
// HMAC-SHA1 期望值,key/data 与 rfc4231Cases 的前四项完全一致。
var rfc2202SHA1Cases = []string{
"b617318655057264e28bc0b6fb378c8ef146be00",
"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
"125d7342b9ac11cd91a39af48aa17b4f63f175d3",
"4c9007f4026250c6bc8414f9bf50c86c2d7235da",
}
// 相同的消息和密钥应该产生相同的HMAC
if hmac1 != hmac2 {
t.Errorf("相同输入产生了不同的HMAC: %s vs %s", hmac1, hmac2)
}
// 不同的消息应该产生不同的HMAC
hmac3 := HMACSHA256("different message", key)
if hmac1 == hmac3 {
t.Errorf("不同消息产生了相同的HMAC")
}
// 不同的密钥应该产生不同的HMAC
hmac4 := HMACSHA256(message, "different_key")
if hmac1 == hmac4 {
t.Errorf("不同密钥产生了相同的HMAC")
// TestSumKnownVectors 用 RFC 4231 与 RFC 2202 的官方测试向量核对摘要计算。
func TestSumKnownVectors(t *testing.T) {
for i, tc := range rfc4231Cases {
t.Run(tc.name, func(t *testing.T) {
if got := hex.EncodeToString(hmacx.SumSHA256(tc.data, tc.key)); got != tc.sha256 {
t.Errorf("HMAC-SHA256 不匹配:\n got %s\nwant %s", got, tc.sha256)
}
if got := hex.EncodeToString(hmacx.SumSHA384(tc.data, tc.key)); got != tc.sha384 {
t.Errorf("HMAC-SHA384 不匹配:\n got %s\nwant %s", got, tc.sha384)
}
if got := hex.EncodeToString(hmacx.SumSHA512(tc.data, tc.key)); got != tc.sha512 {
t.Errorf("HMAC-SHA512 不匹配:\n got %s\nwant %s", got, tc.sha512)
}
if got := hex.EncodeToString(hmacx.SumSHA1(tc.data, tc.key)); got != rfc2202SHA1Cases[i] {
t.Errorf("HMAC-SHA1 不匹配:\n got %s\nwant %s", got, rfc2202SHA1Cases[i])
}
})
}
}
func TestHMACSHA512(t *testing.T) {
message := "hello world"
key := "secret_key"
// TestSumGenericMatchesSpecialized 校验通用入口 Sum 与各专用函数一致。
func TestSumGenericMatchesSpecialized(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
msg := []byte("generic entry point")
// 计算HMAC-SHA512
hmac1 := HMACSHA512(message, key)
// 再次计算相同的消息和密钥
hmac2 := HMACSHA512(message, key)
// 相同的消息和密钥应该产生相同的HMAC
if hmac1 != hmac2 {
t.Errorf("相同输入产生了不同的HMAC: %s vs %s", hmac1, hmac2)
// Sum + sha256.New 必须与 SumSHA256 完全一致。
if got, want := hmacx.Sum(sha256.New, msg, key), hmacx.SumSHA256(msg, key); !bytes.Equal(got, want) {
t.Errorf("Sum(sha256.New) 与 SumSHA256 不一致: %x vs %x", got, want)
}
// 不同的消息应该产生不同的HMAC
hmac3 := HMACSHA512("different message", key)
if hmac1 == hmac3 {
t.Errorf("不同消息产生了相同的HMAC")
// 摘要长度必须正确,否则 Verify* 的长度校验会失效。
if got := len(hmacx.SumSHA1(msg, key)); got != 20 {
t.Errorf("HMAC-SHA1 摘要长度 = %d, 期望 20", got)
}
// 不同的密钥应该产生不同的HMAC
hmac4 := HMACSHA512(message, "different_key")
if hmac1 == hmac4 {
t.Errorf("不同密钥产生了相同的HMAC")
if got := len(hmacx.SumSHA256(msg, key)); got != 32 {
t.Errorf("HMAC-SHA256 摘要长度 = %d, 期望 32", got)
}
if got := len(hmacx.SumSHA384(msg, key)); got != 48 {
t.Errorf("HMAC-SHA384 摘要长度 = %d, 期望 48", got)
}
if got := len(hmacx.SumSHA512(msg, key)); got != 64 {
t.Errorf("HMAC-SHA512 摘要长度 = %d, 期望 64", got)
}
}
func TestHMACSHA256VsSHA512(t *testing.T) {
message := "hello world"
key := "secret_key"
// TestHexFunctions 校验十六进制便利函数与原始摘要一致且格式正确。
func TestHexFunctions(t *testing.T) {
key := []byte("a-key-with-enough-entropy")
msg := []byte("hex convenience")
// 计算HMAC-SHA256和HMAC-SHA512
hmac256 := HMACSHA256(message, key)
hmac512 := HMACSHA512(message, key)
cases := []struct {
name string
got string
raw []byte
}{
{"HexSHA1", hmacx.HexSHA1(msg, key), hmacx.SumSHA1(msg, key)},
{"HexSHA256", hmacx.HexSHA256(msg, key), hmacx.SumSHA256(msg, key)},
{"HexSHA384", hmacx.HexSHA384(msg, key), hmacx.SumSHA384(msg, key)},
{"HexSHA512", hmacx.HexSHA512(msg, key), hmacx.SumSHA512(msg, key)},
}
for _, c := range cases {
if c.got != hex.EncodeToString(c.raw) {
t.Errorf("%s 与原始摘要不一致: %s", c.name, c.got)
}
if c.got != strings.ToLower(c.got) {
t.Errorf("%s 应为小写十六进制: %s", c.name, c.got)
}
if len(c.got) != len(c.raw)*2 {
t.Errorf("%s 长度 = %d, 期望 %d", c.name, len(c.got), len(c.raw)*2)
}
}
}
// SHA256和SHA512应该产生不同的HMAC
if hmac256 == hmac512 {
t.Errorf("SHA256和SHA512产生了相同的HMAC")
// TestVerifyAcceptsValidMAC 校验 Verify* 对正确 MAC 返回 true。
func TestVerifyAcceptsValidMAC(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
msg := []byte("verify the good mac")
if !hmacx.VerifySHA1(msg, key, hmacx.SumSHA1(msg, key)) {
t.Error("VerifySHA1 对正确 MAC 返回了 false")
}
if !hmacx.VerifySHA256(msg, key, hmacx.SumSHA256(msg, key)) {
t.Error("VerifySHA256 对正确 MAC 返回了 false")
}
if !hmacx.VerifySHA384(msg, key, hmacx.SumSHA384(msg, key)) {
t.Error("VerifySHA384 对正确 MAC 返回了 false")
}
if !hmacx.VerifySHA512(msg, key, hmacx.SumSHA512(msg, key)) {
t.Error("VerifySHA512 对正确 MAC 返回了 false")
}
if !hmacx.Verify(sha256.New, msg, key, hmacx.Sum(sha256.New, msg, key)) {
t.Error("Verify(sha256.New) 对正确 MAC 返回了 false")
}
}
// TestVerifyRejectsTamperedMAC 校验单比特翻转会被拒绝(且不 panic)。
func TestVerifyRejectsTamperedMAC(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
msg := []byte("verify the tampered mac")
tamper := func(src []byte) []byte {
out := append([]byte(nil), src...)
out[0] ^= 0x01 // 翻转 1 bit
return out
}
tests := []struct {
name string
good []byte
ver func(message, key, expectedMAC []byte) bool
}{
{"SHA1", hmacx.SumSHA1(msg, key), hmacx.VerifySHA1},
{"SHA256", hmacx.SumSHA256(msg, key), hmacx.VerifySHA256},
{"SHA384", hmacx.SumSHA384(msg, key), hmacx.VerifySHA384},
{"SHA512", hmacx.SumSHA512(msg, key), hmacx.VerifySHA512},
}
for _, tc := range tests {
if tc.ver(msg, key, tamper(tc.good)) {
t.Errorf("Verify%s 接受了被篡改 1 bit 的 MAC", tc.name)
}
// 错误的密钥同样必须被拒绝。
if tc.ver(msg, []byte("another-key-with-32-bytes-len!!"), tc.good) {
t.Errorf("Verify%s 接受了错误密钥下的 MAC", tc.name)
}
// 错误的消息同样必须被拒绝。
if tc.ver([]byte("another message"), key, tc.good) {
t.Errorf("Verify%s 接受了错误消息下的 MAC", tc.name)
}
}
}
// TestVerifyRejectsWrongLengthMAC 校验长度不符时返回 false 而不是 panic。
func TestVerifyRejectsWrongLengthMAC(t *testing.T) {
key := []byte("0123456789abcdef")
msg := []byte("length check")
good := hmacx.SumSHA256(msg, key)
// 长度不足、超长、空切片、nil 都必须安全返回 false。
lengths := [][]byte{
nil,
{},
good[:len(good)-1],
append(append([]byte(nil), good...), 0x00),
make([]byte, 64),
make([]byte, 1),
}
for _, mac := range lengths {
func() {
defer func() {
if r := recover(); r != nil {
t.Fatalf("VerifySHA256 对长度 %d 的 MAC 发生了 panic: %v", len(mac), r)
}
}()
if hmacx.VerifySHA256(msg, key, mac) {
t.Errorf("VerifySHA256 接受了长度 %d(期望 32)的 MAC", len(mac))
}
}()
}
// 其余算法的长度校验同样必须生效。
if hmacx.VerifySHA1(msg, key, good) {
t.Error("VerifySHA1 接受了 32 字节的 MAC(期望 20)")
}
if hmacx.VerifySHA384(msg, key, good) {
t.Error("VerifySHA384 接受了 32 字节的 MAC(期望 48)")
}
if hmacx.VerifySHA512(msg, key, good) {
t.Error("VerifySHA512 接受了 32 字节的 MAC(期望 64)")
}
}
// TestVerifyEdgeCases 覆盖 nil 哈希构造函数等边界输入,确保不 panic。
func TestVerifyEdgeCases(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("边界输入导致 panic: %v", r)
}
}()
var nilHash func() hash.Hash
if hmacx.Verify(nilHash, []byte("msg"), []byte("key"), []byte("mac")) {
t.Error("Verify(nil) 应返回 false")
}
if hmacx.Sum(nilHash, []byte("msg"), []byte("key")) != nil {
t.Error("Sum(nil) 应返回 nil")
}
// 空消息与空密钥是合法输入:HMAC 仍然可以计算,只是空密钥没有认证意义。
if got := len(hmacx.SumSHA256(nil, nil)); got != 32 {
t.Errorf("空消息/空密钥的 HMAC-SHA256 长度 = %d, 期望 32", got)
}
if !hmacx.VerifySHA256(nil, nil, hmacx.SumSHA256(nil, nil)) {
t.Error("VerifySHA256 对空消息/空密钥的正确 MAC 返回了 false")
}
}
// TestSecureCompare 校验常量时间比较函数的行为。
func TestSecureCompare(t *testing.T) {
a := []byte("constant-time-comparison")
if !hmacx.SecureCompare(a, append([]byte(nil), a...)) {
t.Error("SecureCompare 对相同内容返回了 false")
}
if hmacx.SecureCompare(a, []byte("constant-time-comparisou")) {
t.Error("SecureCompare 对仅末字节不同的内容返回了 true")
}
if hmacx.SecureCompare(a, a[:len(a)-1]) {
t.Error("SecureCompare 对长度不同的内容返回了 true")
}
if !hmacx.SecureCompare(nil, nil) {
t.Error("SecureCompare(nil, nil) 应为 true")
}
if hmacx.SecureCompare(nil, []byte{}) != true {
// nil 与空切片长度都为 0,应视为相等。
t.Error("SecureCompare(nil, []byte{}) 应为 true")
}
// 与 Verify 的结果必须一致。
key := []byte("0123456789abcdef")
msg := []byte("consistency")
mac := hmacx.SumSHA256(msg, key)
if !hmacx.SecureCompare(mac, hmacx.SumSHA256(msg, key)) {
t.Error("SecureCompare 对相同 MAC 返回了 false")
}
if !hmacx.VerifySHA256(msg, key, mac) {
t.Error("VerifySHA256 与 SecureCompare 结果不一致")
}
}
// errReader 是一个总是返回错误的 io.Reader,用于测试流式读取的错误路径。
type errReader struct{}
func (errReader) Read([]byte) (int, error) {
return 0, errors.New("模拟的读取失败")
}
// shortReader 在返回全部数据后返回错误,模拟"读一半失败"的场景。
type shortReader struct {
data []byte
}
func (s *shortReader) Read(p []byte) (int, error) {
if len(s.data) == 0 {
return 0, errors.New("模拟的读取中断")
}
n := copy(p, s.data)
s.data = s.data[n:]
return n, nil
}
// TestSumReaderSHA256 校验流式计算与整体计算一致,以及错误路径。
func TestSumReaderSHA256(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
payloads := [][]byte{
nil,
{},
[]byte("a"),
[]byte("streaming payload that is longer than one internal block boundary 0123456789"),
bytes.Repeat([]byte("block"), 1000),
}
for i, payload := range payloads {
got, err := hmacx.SumReaderSHA256(bytes.NewReader(payload), key)
if err != nil {
t.Fatalf("用例 %d: SumReaderSHA256 返回错误: %v", i, err)
}
want := hmacx.SumSHA256(payload, key)
if !bytes.Equal(got, want) {
t.Errorf("用例 %d: SumReaderSHA256 与 SumSHA256 不一致: %x vs %x", i, got, want)
}
if !hmacx.VerifySHA256(payload, key, got) {
t.Errorf("用例 %d: 流式摘要未能通过 VerifySHA256", i)
}
}
// 读取失败必须返回 error 与 nil 摘要,绝不返回部分结果。
got, err := hmacx.SumReaderSHA256(errReader{}, key)
if err == nil {
t.Error("读取失败时 SumReaderSHA256 未返回错误")
}
if got != nil {
t.Errorf("读取失败时 SumReaderSHA256 应返回 nil,实际 %x", got)
}
got, err = hmacx.SumReaderSHA256(&shortReader{data: []byte("partial data")}, key)
if err == nil {
t.Error("读取中断时 SumReaderSHA256 未返回错误")
}
if got != nil {
t.Errorf("读取中断时 SumReaderSHA256 应返回 nil,实际 %x", got)
}
// nil reader 不能 panic。
func() {
defer func() {
if r := recover(); r != nil {
t.Fatalf("nil reader 导致 panic: %v", r)
}
}()
got, err := hmacx.SumReaderSHA256(nil, key)
if err == nil {
t.Error("nil reader 应返回错误")
}
if got != nil {
t.Errorf("nil reader 时应返回 nil,实际 %x", got)
}
}()
}
// TestConstants 校验导出的长度常量为文档约定的值。
func TestConstants(t *testing.T) {
if hmacx.MinKeyLength != 16 {
t.Errorf("MinKeyLength = %d, 期望 16", hmacx.MinKeyLength)
}
if hmacx.DefaultKeyLength != 32 {
t.Errorf("DefaultKeyLength = %d, 期望 32", hmacx.DefaultKeyLength)
}
if hmacx.DefaultKeyLength < hmacx.MinKeyLength {
t.Error("DefaultKeyLength 不应小于 MinKeyLength")
}
}
// TestNoPanicOnAnyInput 用一批畸形输入整体验证不 panic。
func TestNoPanicOnAnyInput(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("畸形输入导致 panic: %v", r)
}
}()
msgs := [][]byte{nil, {}, []byte("x"), bytes.Repeat([]byte{0xff}, 1<<10)}
keys := [][]byte{nil, {}, []byte("k"), bytes.Repeat([]byte{0x00}, 200)}
for _, m := range msgs {
for _, k := range keys {
_ = hmacx.SumSHA1(m, k)
_ = hmacx.SumSHA384(m, k)
_ = hmacx.HexSHA512(m, k)
_ = hmacx.VerifySHA256(m, k, nil)
_ = hmacx.SecureCompare(m, k)
}
}
}