Go crypto/hmac Equal 怎样比较两个签名结果
来源:17golang原创
时间:2026-11-11 13:25:19 147浏览 收藏
在 Go 中比较两个签名结果,重点不是把两个字符串直接用 == 或普通字节比较拼起来,而是先确认它们代表同一种 MAC,再使用 crypto/hmac.Equal。常见场景是:客户端提交一个 MAC,服务端用同一条消息和密钥重新计算出期望值,最后比较两份原始字节。
最稳妥的边界是:传输层的十六进制或 Base64 文本先解码,计算层得到期望 MAC,比较层统一调用 hmac.Equal。两边长度不同直接判定不匹配;编码错误、消息不一致和密钥错误则应在比较前分别处理。
本文只讨论 HMAC 结果怎样比较,不展开密钥生成、完整鉴权协议和第三方平台的签名拼接规则。
先分清“签名文本”和“MAC 字节”
HMAC 的结果本质上是一段字节。接口为了传输方便,往往把它编码为十六进制或 Base64 文本。比如同一份 32 字节 MAC,十六进制文本通常会变成 64 个可见字符;这两个长度并不相同,不能把文本长度直接当作 MAC 长度。
比较前可以先画出两条数据边界:
- 客户端一侧:收到的签名文本经过对应编码解码,得到
clientMAC []byte。 - 服务端一侧:使用约定的哈希函数、密钥和消息重新计算,得到
expectedMAC []byte。 - 比较一侧:只比较这两段原始字节,不把 hex 或 Base64 的外层表示混进来。
使用 hmac.New 计算期望 MAC
hmac.New 接收一个返回新哈希对象的函数和密钥。下面的例子用 SHA-256 计算服务端期望值,关键点是消息必须按协议约定保持一致,不能一边带换行、一边不带换行。

package main
import (
"crypto/hmac"
"crypto/sha256"
)
// expectedMAC 按双方约定的哈希函数、密钥和消息计算期望 MAC。
func expectedMAC(message, key []byte) []byte {
// hmac.New 需要一个每次都能返回新 Hash 的构造函数。
mac := hmac.New(sha256.New, key)
// Write 的错误在标准哈希实现中不会返回,但仍保留返回值接口习惯。
_, _ = mac.Write(message)
// Sum(nil) 返回当前摘要,并不修改哈希对象的内部状态。
return mac.Sum(nil)
}
这里的 expectedMAC 只是计算结果,不等于“验证已经通过”。验证还需要把它和客户端提供的 MAC 放到同一字节表示下比较。
用 hmac.Equal 比较两个原始字节
官方文档建议使用 hmac.Equal 比较两个 MAC。它的接口很小:传入两段 []byte,返回一个布尔值;当长度不一致时,结果就是不匹配。相同长度时,比较过程按 MAC 比较的安全语义处理,不应改成自行逐字节比较。
package main
import "crypto/hmac"
// validMAC 只负责比较已经准备好的原始 MAC 字节。
func validMAC(clientMAC, expected []byte) bool {
// hmac.Equal 的输入必须是两段 MAC 字节,而不是十六进制文本。
return hmac.Equal(clientMAC, expected)
}
如果 clientMAC 比 expected 少一个字节,直接返回 false;这不是“需要补零”的提示,也不能通过截断或填充来凑长度。MAC 的长度是算法和协议的一部分,异常长度应该被当作无效输入。
处理十六进制传输值再比较
如果请求头或 JSON 字段传来的是十六进制文本,先用 encoding/hex 解码。解码失败时直接返回错误,不要把原字符串当成签名继续比较。服务端期望值仍然使用原始消息和密钥计算。

package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
// verifyHexMAC 把十六进制签名还原成字节后,再与期望 MAC 比较。
func verifyHexMAC(message, key []byte, receivedHex string) (bool, error) {
// DecodeString 会在字符非法或长度不成对时返回错误。
clientMAC, err := hex.DecodeString(receivedHex)
if err != nil {
return false, fmt.Errorf("decode MAC: %w", err)
}
// 期望值必须用同一消息和密钥重新计算,不能直接相信客户端文本。
mac := hmac.New(sha256.New, key)
_, _ = mac.Write(message)
expected := mac.Sum(nil)
return hmac.Equal(clientMAC, expected), nil
}
实际项目中可以把“计算期望值”提取成单独函数,避免多个入口各自拼接消息。解码和比较仍应保持两个明确的边界:解码失败先返回错误,解码成功后再判断 MAC 是否匹配。
几个容易误用的边界
能不能用 bytes.Equal?
如果只是普通数据相等性判断,bytes.Equal 很方便;但用于验证 MAC 时应遵循 hmac.Equal 的语义。不要因为两者都返回 bool,就把安全用途和一般业务字段比较混为一谈。
两边都是字符串时能不能直接比较?
只有当两边明确使用同一种规范化编码,并且比较本身不承担 MAC 验证职责时,字符串比较才有意义。更稳妥的做法是把客户端的十六进制或 Base64 文本解码为字节,再比较 MAC。
消息内容一样但结果不同怎么办?
优先检查密钥是否一致、哈希算法是否一致、消息是否多了换行或空格、编码是否解码了两次,以及客户端传来的文本是否被大小写或 URL 编码改写。hmac.Equal 只比较最终字节,不负责修正上游协议差异。
空切片会被当成合法签名吗?
空切片之间可能相等,但这不代表它们是当前协议接受的合法 MAC。是否允许空值属于接口输入规则,应在调用 hmac.Equal 前检查必填字段和算法规定的长度。
小结
Go 中比较 HMAC 结果可以收束为三件事:先把传输文本解码为原始字节;再用同一消息、密钥和哈希函数计算期望值;最后调用 hmac.Equal 判断两段 MAC 是否匹配。长度不一致、解码失败、消息规范化不一致和密钥错误,分别在各自边界处理,不要试图让比较函数替你补救。
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
349 收藏
-
Golang · Go教程 | 18分钟前 | 标准库 · 源码分析 · Go教程 · 错误排查 · Go ast IsValid go/token token.Pos 源码位置 token.NoPos137 收藏
-
Golang · Go教程 | 19分钟前 | 标准库 · 压缩 · Go教程 · reset compress/gzip io.ByteReader Reader.Multistream gzip 多成员209 收藏
-
157 收藏
-
444 收藏
-
368 收藏
-
Golang · Go教程 | 41分钟前 | 标准库 · time · 时区 · Go教程 · Go 本地时间 time.LoadLocation time.Parse time.ParseInLocation 时区解析260 收藏
-
160 收藏
-
345 收藏
-
144 收藏
-
125 收藏
-
335 收藏
-
256 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习