为什么你的API请求总被拒绝?可能是加密没对上
最近帮同事查一个问题,他调第三方天气API,一直返回“签名无效”。折腾半天才发现,是对方要求用HMAC-SHA256做请求参数签名,但他用了MD5。这类问题在对接外部接口时太常见了,表面上看是“请求失败”,根子往往出在数据加密方式不匹配。
最常见的几种API加密方式
现在大多数API都会对传输数据做加密或签名处理,主要为了防篡改、防重放。别一听“加密”就头大,其实就那么几种套路。
1. 对称加密(比如AES)
双方约定一个密钥,发数据前用AES加密,收到后用同一个密钥解密。速度快,适合传敏感内容,比如用户身份证号、银行卡。但密钥得安全传递,不然等于把钥匙挂门口。
2. 非对称加密(比如RSA)
有公钥和私钥。你用对方给的公钥加密,对方用自己的私钥解密。不怕中间人截获,因为只有私钥能解开。常用于传输对称密钥本身,或者做数字签名。
3. 签名验证(比如HMAC、SHA系列)
不是加密数据,而是生成一个“指纹”。比如把所有参数按字典序拼起来,加上密钥,跑一遍HMAC-SHA256,得到一串字符串作为签名,附在请求里。接收方重新算一遍,对不上就说明数据被改过。
实际例子:签名怎么算错了
有次我们对接支付网关,文档写着“使用商户密钥对参数进行HMAC-SHA256签名”。看着简单,结果测试环境老是验签失败。最后发现是排序搞错了——他们要求先按参数名升序排列,但我们用了JSON序列化的默认顺序,而JSON本身不保证顺序。
正确做法:
<?php
$parameters = [
'appid' => 'wx123',
'nonce_str' => 'abc456',
'amount' => 100,
];
// 按参数名排序
ksort($parameters);
// 拼成 key=value&key=value 格式
$stringToSign = '';
foreach ($parameters as $k => $v) {
$stringToSign .= $k . '=' . $v . '&';
}
$stringToSign .= 'key=your_merchant_key'; // 注意最后加商户密钥
$sign = strtoupper(hash_hmac('sha256', $stringToSign, 'your_merchant_key'));
?>
这个例子中,漏掉任何一个步骤,比如没排序、没加key、大小写不对,都会导致签名失败。
HTTPS已经加密了,为啥还要自己加签?
很多人觉得上了HTTPS就万事大吉。其实HTTPS只保证传输过程加密,但服务器没法判断这个请求是不是你发的,还是别人伪造的。加签的作用是身份认证和完整性校验。就像快递员看到包裹完好(HTTPS),还得核对收件码(签名),才敢把东西交给你。
排查建议:从这几个地方下手
遇到API返回“非法请求”“签名错误”“数据已被篡改”这类提示,别急着重试,先检查:
- 加密方式是否一致?是HMAC-SHA1还是SHA256?差一点都不行。
- 密钥有没有填错?测试环境和正式环境的密钥经常不一样,别混用。
- 参数是否参与签名?有些字段如timestamp、nonce必须包含,少一个就算错。
- 编码格式是否统一?中文有没有URL编码?空格是%20还是+?
- 时间戳有没有超时?很多API要求timestamp在5分钟内,本地时间不准也会失败。
最靠谱的办法,是拿官方提供的签名示例,一步步比对中间结果。有时候多一个&,或者字母大小写不对,都能卡你半小时。
别忽视文档里的小字
有家物流平台的API文档,在“注意事项”里写了一句:“签名字符串末尾无需添加‘&’”。我们一开始没注意,拼完参数自动加了个&,结果一直失败。这种细节藏在角落,但直接影响结果。所以看文档别光扫大标题,小字部分才是坑最多的地方。
说到底,API加密不是玄学,就是按规矩办事。谁漏步骤,谁就得花时间踩坑。