惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
O
OpenAI News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog
博客园 - 聂微东
博客园_首页
Latest news
Latest news
Know Your Adversary
Know Your Adversary
雷峰网
雷峰网
Recent Announcements
Recent Announcements
美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tor Project blog
J
Java Code Geeks
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
G
GRAHAM CLULEY
腾讯CDC
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
F
Full Disclosure
The Hacker News
The Hacker News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
K
Kaspersky official blog
Jina AI
Jina AI
Scott Helme
Scott Helme
P
Privacy & Cybersecurity Law Blog
Hugging Face - Blog
Hugging Face - Blog
H
Help Net Security
V2EX - 技术
V2EX - 技术
P
Privacy International News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
小众软件
小众软件
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
Forbes - Security
Forbes - Security
S
Securelist
A
About on SuperTechFans

沈唁志

使用 Trusted Publishing 提升 npm 包发布安全性 Linux 服务器实现 Word 转图片方案 2025|在代码之外,我学会了旅行、牵手和拥抱 使用 GitHub Actions 自动同步 Docker 镜像到 CNB 怎么申请开具中国税收居民身份证明? Bitwarden Secrets Manager:简化 DevOps 的机密管理 MySQL 字符集与大小写敏感性解析 在命令行中输出带颜色的日志 PHP 中生成带毫秒的时间戳
HMAC 签名编码的坑:Go 和 PHP 的不同处理方式
沈唁 · 2025-03-06 · via 沈唁志

在开发过程中,我们经常使用 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 代码

<?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 进制字符串

  1. 原始二进制数据
    • 在 Go 代码中,signatureBytes 是 HMAC 计算出的二进制数据。
    • 在 PHP 代码中,hash_hmac('sha1', $data, $password, true) 也返回二进制数据。
    • 直接对这些二进制数据进行 Base64 编码,输出的是编码后的 HMAC 结果。
  2. 16 进制字符串转换
    • 在 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 签名在不同语言中的编码差异,并在开发中避免类似的问题!