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) } } }