HMAC 签名编码的坑:Go 和 PHP 的不同处理方式
版权声明
我们非常重视原创文章,为尊重知识产权并避免潜在的版权问题,我们在此提供文章的摘要供您初步了解。如果您想要查阅更为详尽的内容,访问作者的公众号页面获取完整文章。
鲁飞
扫码关注公众号
扫码阅读
手机扫码阅读
在开发过程中,我们经常使用 HMAC(散列消息认证码)对数据进行签名,以确保数据完整性和身份验证。
然而,不同编程语言在对签名数据进行编码时可能会有所不同,导致相同的 HMAC 计算在不同语言中产生不同的结果。
这篇文章也是因为我直接将 PHP 的签名算法扔给 ChatGPT 生成,并没有实际测试,导致客户反馈签名计算失败,测试后才发现的。
本文将以 Go 和 PHP 为例,探讨为什么直接对 HMAC 签名进行 Base64 编码与先转换为 16 进制字符串再编码的结果不同。
代码示例
Go 代码
package main import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "encoding/hex" "fmt" ) func main() { data := "hello" password := "123456" h := hmac.New(sha1.New, []byte(password)) h.Write([]byte(data)) signatureBytes := h.Sum(nil) // 直接对 HMAC 结果进行 Base64 编码 base64Signature := base64.StdEncoding.EncodeToString(signatureBytes) fmt.Println(base64Signature) // 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ= // 先转换成 16 进制字符串,再进行 Base64 编码 hexString := hex.EncodeToString(signatureBytes) base64OfHex := base64.StdEncoding.EncodeToString([]byte(hexString)) fmt.Println(base64OfHex) // 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA== }
PHP 代码
$data = "hello"; $password = "123456"; // 直接对 HMAC 结果进行 Base64 编码 echo base64_encode(hash_hmac('sha1', $data, $password, true)); // 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ= echo "\n"; // 先转换成 16 进制字符串,再进行 Base64 编码 echo base64_encode(hash_hmac('sha1', $data, $password)); // 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA== ?>
为什么结果不同?
表面上看,Go 和 PHP 代码的逻辑是相同的,但它们的 Base64 结果却不同。
其根本原因在于编码前的输入数据不同。
PHP 参数定义文档
hash_hmac( string $algo, string $data, #[\SensitiveParameter] string $key, bool $binary = false ): string
PHP 手册中也提到了:当 binary 设置为 true 输出原始二进制数据,设置为 false 输出小写 16 进制字符串。
原始二进制 vs. 16 进制字符串
原始二进制数据
在 Go 代码中, signatureBytes是 HMAC 计算出的二进制数据。在 PHP 代码中, hash_hmac('sha1', $data, $password, true)也返回二进制数据。直接对这些二进制数据进行 Base64 编码,输出的是编码后的 HMAC 结果。
在 PHP 中, hash_hmac('sha1', $data, $password)默认返回 16 进制字符串,每个字节被转换成 2 个字符。在 Go 中, hex.EncodeToString(signatureBytes)也会将二进制数据转换为 16 进制字符串。由于 16 进制字符串的长度是原始二进制数据的 2 倍,在进行 Base64 编码时,最终结果也会完全不同。
Base64 编码的作用
Base64 编码的主要作用是将二进制数据转换为文本格式,便于在 URL 或 JSON 等环境中传输。
它不会改变数据的内容,而是按照固定的方式将每 3 个字节转换为 4 个可打印字符。
因此,输入数据的不同会直接影响最终的编码结果。
如何保证一致性?
如果希望跨语言 HMAC 计算保持一致,建议:
确保 Base64 编码前的数据格式一致,统一使用二进制数据进行编码。 在 PHP 中,使用 hash_hmac('sha1', $data, $password, true)以获取二进制结果。在 Go 中,直接使用 base64.StdEncoding.EncodeToString(signatureBytes),避免中间转换为 16 进制字符串。
结论
直接对 HMAC 结果进行 Base64 编码,能保持原始数据格式,保证数据可还原。 先转换为 16 进制字符串再进行 Base64 编码,会导致数据翻倍,最终的编码结果不同。 在不同语言间使用 HMAC 签名时,务必保证编码方式的一致性,以避免验证失败。
希望这篇文章能帮助你理解 HMAC 签名在不同语言中的编码差异,并在开发中避免类似的问题!
鲁飞
鲁飞
扫码关注公众号
还在用多套工具管项目?
一个平台搞定产品、项目、质量与效能,告别整合之苦,实现全流程闭环。
查看方案
鲁飞的其他文章
对接腾讯云实时音视频(TRTC)云端录制
使用腾讯云TRTC的服务端的 REST API 实现云端录制。
为 Docsify 自动生成 RSS 订阅
为Docsify等静态站点自动生成RSS订阅
腾讯企业邮箱收不到邮件怎么回事
原因就是域名解析 CNAME 和 MX 记录冲突所导致的。
GitHub Actions 真香系列之自动同步镜像仓库
GitHub虽然在国内访问慢,但是依旧不能阻挠国内开发者的使用,我们一般也会在Gitee或者其他托管平台创建一个镜像,用来方便不能正常访问GitHub的开发者。
MySQL 字符集与大小写敏感性解析
在 MySQL 数据库中,UTF-8 及其变体是最常用的字符集。
加入社区微信群
与行业大咖零距离交流学习
PMO实践白皮书
白皮书上线
白皮书上线