Files
encryptx/hmacx/hmacx_test.go
T
2026-09-30 15:49:12 +08:00

414 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package hmacx_test
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"hash"
"strings"
"testing"
"code.yun.ink/pkg/encryptx/hmacx"
)
// 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
}
// 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",
},
}
// rfc2202SHA1Cases 为 RFC 2202(HMAC-MD5/HMAC-SHA-1 测试向量)Test Case 1~4 的
// HMAC-SHA1 期望值,key/data 与 rfc4231Cases 的前四项完全一致。
var rfc2202SHA1Cases = []string{
"b617318655057264e28bc0b6fb378c8ef146be00",
"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
"125d7342b9ac11cd91a39af48aa17b4f63f175d3",
"4c9007f4026250c6bc8414f9bf50c86c2d7235da",
}
// 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])
}
})
}
}
// TestSumGenericMatchesSpecialized 校验通用入口 Sum 与各专用函数一致。
func TestSumGenericMatchesSpecialized(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
msg := []byte("generic entry point")
// 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)
}
// 摘要长度必须正确,否则 Verify* 的长度校验会失效。
if got := len(hmacx.SumSHA1(msg, key)); got != 20 {
t.Errorf("HMAC-SHA1 摘要长度 = %d, 期望 20", got)
}
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)
}
}
// TestHexFunctions 校验十六进制便利函数与原始摘要一致且格式正确。
func TestHexFunctions(t *testing.T) {
key := []byte("a-key-with-enough-entropy")
msg := []byte("hex convenience")
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)
}
}
}
// 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)
}
}
}