如何用Node.js crypto模块实现PHP hash_hmac验证Webhook签名
Webhook签名验证:JS与PHP不匹配的解决方案
问题根源
PHP代码直接读取原始请求体(php://input)计算HMAC,而你的JavaScript代码用JSON.stringify(req.body)生成签名内容——这会导致两个核心差异:
- 框架(如Express)解析
req.body时,可能自动调整原始JSON的格式(比如缩进、键的排序、多余空格的处理) JSON.stringify的输出和原始请求体的字节流无法保证完全一致
修复方案
不要依赖解析后的req.body,直接获取原始请求体的字节流来计算签名:
步骤1:配置中间件保留原始请求体(以Express为例)
禁用默认的json中间件,改用raw中间件获取未修改的请求体:
const express = require('express'); const crypto = require('crypto'); const app = express(); // 用raw中间件捕获原始请求体,指定处理application/json类型 app.use(express.raw({ type: 'application/json' }));
步骤2:修改签名验证逻辑
直接使用原始请求体Buffer计算HMAC,避免JSON序列化带来的差异:
const secret = "SUPERSECRETKEY"; app.post('/webhook', (req, res) => { const sig = req.headers['sig']; // 直接使用req.body(此时为原始Buffer) const hash = "sha256=" + crypto.createHmac('sha256', secret) .update(req.body) .digest('hex'); if (hash === sig) { // 若需处理请求数据,可在此手动解析原始Buffer const bodyData = JSON.parse(req.body.toString()); res.status(200).send("OK"); } else { res.status(401).send("Unauthorized"); } }); app.listen(3000);
额外注意事项
- 确保请求的
Content-Type为application/json,否则raw中间件无法正确捕获请求体 - 确认签名格式完全匹配:PHP和JS生成的十六进制哈希均为小写,需保证请求头的
sig前缀(sha256=)和哈希部分大小写一致
内容的提问来源于stack exchange,提问作者Marius
相关产品推荐
相关产品推荐

