Node.js Crypto.js处理阿拉伯语/带重音字符签名异常问题
解决Node.js Crypto HMAC SHA256跨系统签名不匹配问题
核心问题分析
你的问题出在两个关键点:
- JSON.stringify(req.body)引入序列化差异:不同系统(或不同JSON解析/序列化实现)对非ASCII字符的转义、对象属性的序列化顺序可能存在细微差别,导致最终用于计算签名的字符串不一致。Mac系统的JSON序列化行为恰好和服务商一致,而Windows/Linux则不同。
- 编码的隐式处理:虽然Node.js的
crypto.update默认使用UTF-8,但如果请求体解析或字符串序列化过程中编码被篡改,也会导致签名不匹配。
解决方案
方案1:使用原始请求体Buffer计算签名(推荐)
服务商大概率是直接基于原始HTTP请求体的二进制数据计算签名,而非解析后再序列化的JSON字符串。因此直接使用原始请求体Buffer计算,能完全避免序列化差异问题。
- 配置Express获取原始请求体:
import express from 'express'; const app = express(); // 先添加raw中间件捕获原始请求体(Buffer类型) app.use(express.raw({ type: 'application/json' })); // 再解析JSON到req.body(如果业务逻辑需要使用解析后的对象) app.use(express.json());
- 修改签名验证逻辑:
import crypto from "crypto"; const secret = "secret"; const validateHeaders = (req, res, next) => { // 使用原始请求体Buffer计算签名 const hash = crypto .createHmac("sha256", secret) .update(req.body) // 此时req.body是raw中间件提供的原始Buffer .digest("hex"); if (hash === req.headers["_signature"]) { return next(); } return res.sendStatus(401); };
方案2:强制统一JSON序列化行为(若无法使用原始请求体)
如果必须基于解析后的req.body对象计算,需确保JSON序列化行为和服务商完全一致:
- 显式指定JSON.stringify的参数,消除格式差异:
// 使用无缩进、无空格的序列化,保留原始UTF-8字符(不转义为Unicode序列) const serializedBody = JSON.stringify(req.body, null, 0);
- 显式指定
crypto.update的编码为UTF-8:
const hash = crypto .createHmac("sha256", secret) .update(serializedBody, 'utf8') .digest("hex");
- 确保请求体解析时使用UTF-8编码:
app.use(express.json({ encoding: 'utf8' }));
验证要点
- 对比你生成的序列化字符串和服务商使用的字符串是否完全一致(包括字符编码、空格、换行、属性顺序)
- 测试非ASCII字符时,直接打印
serializedBody或原始Buffer的UTF-8字符串,确认和服务商的原始请求体内容完全匹配
内容的提问来源于stack exchange,提问作者Assad Anabosi
相关产品推荐
相关产品推荐

