
本文详解 go 语言中生成 hmac-sha256 签名的规范方法,指出将密钥误用 md5 哈希原始字节而非其十六进制字符串表示所导致的典型错误,并提供可直接运行的修复代码与最佳实践。
本文详解 go 语言中生成 hmac-sha256 签名的规范方法,指出将密钥误用 md5 哈希原始字节而非其十六进制字符串表示所导致的典型错误,并提供可直接运行的修复代码与最佳实践。
在 Go 中使用 crypto/hmac 包生成 HMAC-SHA256 签名时,一个极易被忽视的关键点是:密钥(key)必须是业务逻辑中约定的原始字节序列,而非其十六进制编码后的字符串。原代码中通过 md5.New() 生成密钥并打印 hex.EncodeToString(key),看似得到了“密钥字符串”,但后续却直接将该 MD5 的原始字节(16 字节)作为 HMAC 的 key —— 这会导致签名结果与预期(如其他语言或服务端)完全不一致。
问题本质在于混淆了「密钥表示」与「密钥本体」。例如,字符串 "secret" 的 MD5 值为 5ebe2294ecd0e0f08eab7690d2a6ee69,这是一个 32 字符的十六进制字符串;而 md5.Sum(nil) 返回的是 16 字节的原始切片 []byte{0x5e, 0xbe, ...}。若外部系统期望以 "5ebe2294ecd0e0f08eab7690d2a6ee69" 作为密钥(即按字符串字面量使用),那么 Go 中就必须显式将其解码为字节:
package main
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)
func makeSig() string {
// ✅ 正确方式:若约定密钥为 secret 的 MD5 十六进制字符串,则先生成该字符串,再 hex.DecodeString
secret := "secret"
secretHash := md5.Sum([]byte(secret)) // 使用 Sum 而非 New + Write + Sum(nil),更简洁安全
keyHex := fmt.Sprintf("%x", secretHash) // 得到 "5ebe2294ecd0e0f08eab7690d2a6ee69"
// 将十六进制字符串解码为原始字节(32 字符 → 16 字节)
key, err := hex.DecodeString(keyHex)
if err != nil {
panic(err)
}
message := strings.Join([]string{"one", "two", "three"}, "")
// 使用解码后的字节作为 HMAC key
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
signature := hex.EncodeToString(h.Sum(nil))
fmt.Printf("Key (hex): %s\n", keyHex)
fmt.Printf("Key (bytes len): %d\n", len(key))
fmt.Printf("Message: %s\n", message)
fmt.Printf("HMAC-SHA256: %s\n", signature)
return signature
}
func main() {
makeSig()
}? 关键提醒:
- 不要直接用 md5.New().Write(...).Sum(nil) 的输出作为 HMAC key,除非你明确知道该二进制值就是协议要求的原始密钥;
- 若文档/接口说明密钥为 "abc123..." 类十六进制字符串,请务必 hex.DecodeString() 后再传入 hmac.New();
- 更推荐的做法是避免对密钥做哈希预处理——直接使用强随机密钥(如 []byte("my-super-secret-key-32-bytes-long")),既安全又无歧义;
- 生产环境应使用 crypto/rand 生成密钥,并通过环境变量或密钥管理服务注入,而非硬编码。
综上,HMAC 的安全性与正确性高度依赖密钥的精确字节一致性。调试时建议打印 len(key) 和 fmt.Printf("%x", key) 进行双向验证,确保 Go 与其他系统使用的 key 完全等价。遵循此原则,即可稳定生成跨平台兼容的 HMAC-SHA256 签名。










