hmac.new 第一个参数必须是哈希函数构造器(如 sha256.new),而非实例(如 sha256.new());验证签名须用 hmac.equal 防时序攻击;密钥应≥哈希输出长度且随机;webhook 必须绑定时间戳或 nonce 防重放。

hmac.New 创建实例时,哈希函数构造器不能传错类型
Go 的 hmac.New 第二个参数是密钥字节切片,第一个参数必须是「哈希函数构造器」,不是哈希函数实例。常见错误是传了 sha256.New()(正确) vs sha256.Sum256{} 或 sha256.New().Sum(nil)(错误),后者会直接 panic 或编译失败。
-
hmac.New(sha256.New, key)✅ 正确:传的是函数指针func() hash.Hash -
hmac.New(sha256.New(), key)❌ 编译报错:cannot use sha256.New() (value of type hash.Hash) as func() hash.Hash value - SHA-512、SHA-3 等同理,必须用
sha512.New(无括号),而非sha512.New()
验证签名必须用 hmac.Equal,别用 == 或 bytes.Equal
直接用 == 比较两个 []byte HMAC 值会触发时序攻击风险——攻击者可通过毫秒级响应时间差异逐字节猜出合法签名。Go 标准库的 hmac.Equal 是常量时间比较,从 Go 1.3 起内置,但容易被忽略。
- ✅ 正确写法:
hmac.Equal(expectedMAC, receivedMAC) - ❌ 危险写法:
expectedMAC == receivedMAC(语法错误,[]byte不支持==)或bytes.Equal(expectedMAC, receivedMAC)(非常量时间) - 如果遇到
undefined: hmac.Equal,先运行go version确认 ≥ 1.3;老项目升级后需清理$GOROOT/pkg缓存
密钥长度和编码方式直接影响安全性,别硬编码字符串密钥
HMAC 安全性高度依赖密钥质量。用短字符串(如 "abc")或可读 ASCII 密钥,等于把门锁换成纸糊的。RFC 2104 明确建议密钥长度 ≥ 哈希输出长度(SHA-256 推荐 ≥32 字节)。
- ❌ 不安全:
key := []byte("my-key")(仅 6 字节,易暴力破解) - ✅ 更稳妥:
key, _ := hex.DecodeString("a1b2c3...")或从环境变量加载 base64 编码的随机密钥 - 注意:密钥若长于哈希块大小(SHA-256 是 64 字节),
hmac.New内部会先哈希一次;但主动控制密钥长度更可靠
Webhook 场景下,必须绑定时间戳或 nonce 防重放
HMAC 本身只防篡改、不防重放。攻击者截获一次合法请求(含 HMAC),稍后重发,服务端仍会验签通过。真实接口(如 GitHub Webhook、Stripe)都强制要求 timestamp 或 X-Hub-Signature-256 + X-Hub-Timestamp 组合校验。
立即学习“go语言免费学习笔记(深入)”;
- 签名时拼入时间戳:
data := fmt.Sprintf("%s:%d", payload, time.Now().Unix()) - 验证时检查时间差是否 ≤ 5 分钟,并拒绝已见过的
nonce - 别省略这步——哪怕你用的是 SHA-256,没防重放就等于没上锁
真正难的不是写对 hmac.New,而是让密钥管理、时间窗口、比较方式、传输编码全部闭环。漏掉任意一环,HMAC 就只剩“看起来很安全”。










