PHP签名必须严格按顺序拼接HTTP方法、URI路径、排序编码参数、时间戳和nonce,用\n分隔并rawurlencode键值,密钥需base64_decode后传入hash_hmac,否则验签必败。

PHP框架中拼接API接口签名必须严格遵循服务端与客户端约定的字段顺序、编码方式和分隔规则,否则hash_hmac计算出的签名值必然不一致,导致验签失败。
确定签名原文的字段组成
签名原文不是把所有参数堆在一起,而是有明确上下文边界的字符串。必须包含且仅包含以下五项(缺一不可):HTTP方法大写形式、请求URI路径(不含查询参数)、排序后的标准化查询/表单参数字符串、UNIX秒级时间戳、nonce随机字符串。
漏掉timestamp或nonce,重放攻击就无法防御;URI带了query string,服务端解析路径时会截断,两边拼出来的字符串长度直接差几十字节。
示例:GET /api/v1/user → URI取 /api/v1/user,不是 /api/v1/user?uid=123。
立即学习“PHP免费学习笔记(深入)”;
参数标准化与排序拼接
方法一:GET/POST混合参数统一处理
先合并$_GET和$_POST(或$this->request->param()),再用ksort($params, SORT_STRING)按键名字典序升序排列。注意:空值字段如'page' => ''也要保留,不能unset。
方法二:JSON请求体特殊处理
若Content-Type含application/json,需用file_get_contents('php://input')或$request->getContent()获取原始body,再做JSON规范化:json_encode(json_decode($body, true), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK)。浮点数3.0必须转为3,否则bin2hex对比时多出“.0”两个字节。
方法三:键值对拼接格式
推荐用\n换行符分隔(非|或&),每行格式为key=value,value必须rawurlencode()单独编码,不能对整个字符串urlencode()——后者会把=和&也编码,破坏结构。【rawurlencode()必须逐键逐值调用】
构造最终签名字符串
第一步:拼出标准化参数块
遍历排序后的$params,对每个$key和$value分别rawurlencode(),拼成$key=rawurlencode($value),再implode("\n", $lines)。
第二步:组合完整$string_to_sign
$string_to_sign = strtoupper($method) . "\n" . $uri . "\n" . $normalized_params . "\n" . $timestamp . "\n" . $nonce;
第三步:确认换行符是\n,不是\r\n。Windows开发环境容易混入\r,可用bin2hex($string_to_sign)检查末尾是否为0a,不是0d0a。
这一步操作起来很简单,直接把文件拖进去就行。
调用hash_hmac生成可传输签名
方法一:基础调用(适用于密钥已为二进制)
$binary_sig = hash_hmac('sha256', $string_to_sign, $secret_key, true); $signature = base64_encode($binary_sig);
方法二:密钥需base64解码(90%线上问题根源)
API提供方给的密钥通常是base64字符串(如长度43),必须先$raw_key = base64_decode($api_secret); 再传入hash_hmac。不这样做,strlen($raw_key)≠32或64,签名永远无效。
方法三:防错校验
if (false === $raw_key || strlen($raw_key)



















