最稳妥的是用hash_hmac('sha256', $string_to_sign, $secret, true)生成二进制签名再base64_encode,禁用md5或sha1拼接,密钥须从配置读取且trim()去BOM和空格。

签名用什么函数生成最稳妥
PHP 接口签名最常用、也最不容易出错的方式是用 hash_hmac(),而不是 md5() 或 sha1() 拼接字符串。前者带密钥、防篡改,后者裸哈希容易被重放或暴力猜解。
关键点:必须用 HMAC-SHA256(或至少 HMAC-SHA1),密钥($secret)不能硬编码在代码里,应从配置或环境变量读取。
- 别用
md5($data . $secret)—— 不抗长度扩展攻击,且顺序可被调换 - 别把
$secret写死在接口文件里,上线后密钥泄露就全废了 - 签名原文必须按固定顺序拼接(如字典序排序参数),否则客户端和服务端结果不一致
签名原文怎么拼才不会和客户端对不上
签名原文不是直接把整个请求体丢进去,而是提取关键字段,按约定规则拼成字符串。常见错误是漏掉时间戳、忽略空值处理、参数排序不一致。
推荐做法:只取 GET 或 POST 中的业务参数(排除 sign、timestamp 本身),剔除空值,按键名升序排列,用 urldecode 统一解码后再拼接。
立即学习“PHP免费学习笔记(深入)”;
示例拼接逻辑:
ksort($params);
$buff = '';
foreach ($params as $k => $v) {
if ($k !== 'sign' && $v !== '' && $v !== null) {
$buff .= $k . '=' . rawurlencode((string)$v) . '&';
}
}
$buff = rtrim($buff, '&');
$sign = hash_hmac('sha256', $buff, $secret);
怎么验证 timestamp 防重放但又不卡死用户
单纯校验 timestamp 是否存在没用,必须限制时间窗口,否则攻击者截获一个合法请求,5 分钟内重发照样通过。
服务端收到请求后,立刻用 time() 对比 timestamp,允许偏差建议设为 ±300 秒(5 分钟),超出即拒。
- 别用
date('U')或$_SERVER['REQUEST_TIME']做对比——精度不够或受时区影响 - 别把时间判断写在签名验证之后——先过期就别浪费 CPU 算 HMAC 了
- 客户端时间不准很常见,±300 秒是平衡安全与体验的底线,低于 180 秒会明显增加失败率
签名验证失败时该怎么返回才不暴露细节
不要返回 "sign error: invalid timestamp" 或 "sign error: missing param app_id" —— 这等于告诉攻击者哪一步错了,方便针对性绕过。
统一返回模糊错误,例如 {"code":401,"msg":"Unauthorized"},HTTP 状态码用 401,且日志里单独记录完整失败原因(供排查用,不返回给前端)。
额外注意:验证必须在所有业务逻辑之前完成,包括数据库查询、缓存读取等;否则签名无效却已触发副作用,可能引发状态不一致。
签名这层看似简单,真正难的是参数归一化、时钟同步、密钥轮换和错误反馈粒度——这些地方一松动,整个签名机制就形同虚设。



















