Go crypto/hmac Equal 怎样比较两个签名结果
在 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 是否匹配。长度不一致、解码失败、消息规范化不一致和密钥错误,分别在各自边界处理,不要试图让比较函数替你补救。
Go token.Pos IsValid 怎样判断源码位置是否有效
- 上一篇
- Go token.Pos IsValid 怎样判断源码位置是否有效
- 下一篇
- sync Pool 取出的对象为什么必须重新初始化
-
- Golang · Go教程 | 25分钟前 | 标准库 · 压缩 · Go教程 · reset compress/gzip io.ByteReader Reader.Multistream gzip 多成员
- compress/gzip Reader.Multistream 怎样控制拼接 gzip 成员的读取
- 209浏览 收藏
-
- Golang · Go教程 | 35分钟前 | 标准库 · Go教程 · Go 复数 go/constant 编译期常量 UNKNOWN constant.Sign
- Go go/constant.Sign 怎样判断未知值和复数的符号结果
- 157浏览 收藏
-
- Golang · Go教程 | 43分钟前 | 标准库 · go · 正则表达式 · 性能 Go AppendText regexp.Regexp
- Go regexp.Regexp AppendText 如何复用缓冲区输出正则
- 368浏览 收藏
-
- Golang · Go教程 | 47分钟前 | 标准库 · time · 时区 · Go教程 · Go 本地时间 time.LoadLocation time.Parse time.ParseInLocation 时区解析
- time ParseInLocation 为什么比 Parse 更适合本地时间
- 260浏览 收藏
-
- Golang · Go教程 | 48分钟前 |
- html/template 模板中如何安全输出 URL 属性
- 160浏览 收藏
-
- Golang · Go教程 | 52分钟前 |
- text/template missingkey 选项如何让模板缺字段报错
- 345浏览 收藏
-
- Golang · Go教程 | 56分钟前 |
- strings Cut 与 SplitN 如何选择首个分隔符解析
- 144浏览 收藏
-
- Golang · Go教程 | 59分钟前 | 错误处理 · Go教程 · Go archive/tar io.EOF 文件遍历 Reader.Next tar归档
- Go archive/tar Reader Next 读到文件尾后怎样处理 EOF
- 125浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- strconv QuoteRune 如何把单个字符转成可打印 Go 字面量
- 256浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 981次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 986次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1105次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1074次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 913次使用
-
- GScript 编写标准库示例详解
- 2022-12-30 369浏览
-
- 关于Golang标准库flag的全面讲解
- 2023-02-25 344浏览
-
- Golang标准库unsafe源码解读
- 2022-12-29 464浏览
-
- 快速掌握Go语言HTTP标准库的实现方法
- 2022-12-30 327浏览
-
- 解析golang 标准库template的代码生成方法
- 2022-12-24 349浏览

