小程序sign验签需服务端用相同参数、时间戳、随机串、密钥及hmac_sha256算法重新计算并hash_equals比对;须校验时间偏移≤300秒、参数字典序拼接、UTF-8编码一致、密钥不泄露。

小程序 sign 字段怎么验?先看它从哪来
小程序端调用 wx.request 发起请求时,如果服务端要求签名验证,通常由前端按约定规则拼接参数 + 时间戳 + 随机串 + 密钥,再用 sha256 或 hmac_sha256 生成 sign。后端必须用完全一致的原始数据和算法重新计算比对,否则必失败。
常见错误现象:sign mismatch、invalid signature、验签通过但偶尔失败。
- 前端传的
timestamp必须和服务端time()对齐(建议允许 ≤ 300 秒偏移) - 所有参与签名的参数必须按字典序升序排列 key,再拼成
key1=value1&key2=value2形式(空值也要参与,不能忽略) - 密钥
$secret绝不能硬编码在前端,应仅存于服务端配置中 - 若使用
hmac_sha256,PHP 必须用hash_hmac('sha256', $data, $secret),不能用sha256($data . $secret)
PHP 里用 hash_hmac 还是 sha256?看小程序 SDK 文档
微信官方未强制签名算法,实际项目中绝大多数用 hmac_sha256,因其抗碰撞性强且密钥不参与明文拼接。
- 小程序侧若用
crypto.createHmac('sha256', secret).update(data).digest('hex'),PHP 端必须对应hash_hmac('sha256', $data, $secret, true)再bin2hex() - 若小程序用
sha256(data + secret)(不推荐),PHP 得写hash('sha256', $data . $secret),但这种易受长度扩展攻击 - 注意:
hash_hmac第四个参数为true时返回二进制,false返回十六进制字符串;前后端必须一致
示例片段:
立即学习“PHP免费学习笔记(深入)”;
$raw = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$expectedSign = hash_hmac('sha256', $raw, $secret, true);
$expectedSign = bin2hex($expectedSign);
if (!hash_equals($expectedSign, $_POST['sign'])) {
die('invalid signature');
}
验签前必须做这三件事:参数过滤、时间校验、字符标准化
签名本身只是最后一步,前面任一环节出错都会导致验签失败或安全漏洞。
- 所有参与签名的参数(如
openid、scene、timestamp)必须从$_GET或$_POST显式取出,不能直接用$_REQUEST - 检查
timestamp是否在服务端当前时间 ±300 秒内,超时直接拒绝(防重放) - 统一编码:确保前后端都用 UTF-8 处理字符串,特别注意中文、emoji、URL 编码差异。PHP 中可用
urldecode()或rawurldecode(),取决于前端如何编码(微信基础库默认用encodeURIComponent,对应 PHP 的rawurldecode)
为什么 hash_equals 不能换成 ===?
用 === 比较签名会引发时序攻击风险——攻击者可通过响应时间差异逐步推断出正确签名。
-
hash_equals($expected, $input)是 PHP 内置的恒定时间比较函数,无论输入是否相等,执行时间基本一致 - 它要求两个参数都为字符串,且长度不同会直接返回
false,所以务必确保$input是合法 hex 字符串(可先用ctype_xdigit()判断) - 如果小程序传的
sign是 base64 而非 hex,PHP 端得先base64_decode(),再用hash_equals比较二进制结果(此时第一个参数也得是二进制)
真正容易被忽略的是:签名原文的构造顺序和编码方式,往往前端调试用 console.log 打印的是“看起来一样”的字符串,但实际多了一个空格、换行、不可见 Unicode 字符,或 URL 编码层级不一致 —— 这类问题只能靠服务端打日志对比原始字节才能定位。











