登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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 计算服务端期望值,关键点是消息必须按协议约定保持一致,不能一边带换行、一边不带换行。

消息和密钥进入 hmac.New 后得到期望 MAC 的静态关系说明图
图1:消息、密钥、哈希函数与期望 MAC 的关系说明图,不是运行截图或输出证据。
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 解码。解码失败时直接返回错误,不要把原字符串当成签名继续比较。服务端期望值仍然使用原始消息和密钥计算。

十六进制签名文本经过解码后与期望 MAC 比较的输入边界说明图
图2:传输文本、解码结果、长度契约和 hmac.Equal 的边界说明图,不是运行截图或验证结果。
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 是否匹配。长度不一致、解码失败、消息规范化不一致和密钥错误,分别在各自边界处理,不要试图让比较函数替你补救。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>