API签名验证的三种套路——以及我们怎么被第三种坑了半天

API签名验证的三种常见模式——密钥追加型、HMAC型、密钥参与排序型。以及为什么你的签名在一个接口能用但另一个不行,可能只是一个排序问题。

标签:API签名MD5踩坑开发经验安全
专属插画
API签名验证的三种套路——以及我们怎么被第三种坑了半天

你调一个第三方API,签名死活不对——然后你发现文档根本没写清楚

这是一个真实故事。今天下午,我们在对接一个DNS管理平台的API,需要通过API自动创建DNS记录。API文档写着:"使用MD5签名验证"。

听起来很简单对吧?把参数拼起来,加上密钥,MD5一下,完事。

我们照做了。列表接口能用。查询接口能用。创建记录——签名错误。

反复检查了两个小时。

API签名的三种常见模式

在聊我们踩的坑之前,先科普一下:API签名验证大致分三种套路。

模式一:密钥追加型(最常见)

这是最直觉的方式,也是大多数国内API的选择。

sign = md5(param1=value1&param2=value2 + secretKey)

把所有参数按字母排序拼成字符串,然后在末尾追加密钥,整体做MD5。微信支付、支付宝开放平台基本都是这个路子。

优点是简单直观,缺点是密钥直接参与哈希,如果哈希算法被攻破(MD5理论上已经不够安全),密钥有泄露风险。

模式二:HMAC型(最安全)

sign = HMAC-SHA256(secretKey, param1=value1&param2=value2)

AWS、Google Cloud、Azure用的都是这种。密钥作为HMAC的key,不直接拼进字符串里。

安全性最高,但实现复杂度也最高——你需要处理canonicalized request、timestamp、nonce等一堆东西。AWS Signature V4的文档长到能当枕头。

模式三:密钥参与排序型(最阴间)

sign = md5(所有参数INCLUDING secretKey一起按字母排序拼接)

注意区别:这里密钥不是追加在末尾,而是作为一个普通参数,和其他参数一起参与字母排序

举个例子,如果你的参数是:

domain=example.com
line=default
secretKey=abc123
top=A
value=1.2.3.4

签名字符串是:domain=example.com&line=default&secretKey=abc123&top=A&value=1.2.3.4

而不是:domain=example.com&line=default&top=A&value=1.2.3.4abc123

看到区别了吗?第一种,secretKey排在line和top之间(按字母序s在l和t之间)。第二种,secretKey追加在最后。

这就是坑我们半天的地方。

为什么列表接口能用但创建接口不行?

这是最精妙的部分。

列表接口(Record.List)参数很少,大概就三四个。碰巧这些参数的首字母排序后,secretKey正好排在最后面。所以不管你是"追加在末尾"还是"参与排序",签名结果是一样的。

纯属巧合。

但创建接口(Record.Create)参数有七八个,其中有些参数的首字母在s后面(比如top、value)。这时候,两种算法的签名结果就不同了。

这种"部分接口能用、部分不能用"的情况是最难排查的——因为你会以为自己的签名逻辑是对的(毕竟列表接口验证通过了),然后把时间浪费在检查参数格式、编码方式、HTTP headers等完全不相关的地方。

怎么破?

最后解决方案很简单:找到API的Swagger/OpenAPI文档,仔细看签名说明,发现了那行关键的小字——"secretKey作为普通参数参与排序"。

几个经验教训:

  1. 别猜签名算法。哪怕"大概知道是MD5签名",具体拼接方式不看文档就是赌运气。
  2. 一个接口通过不代表签名逻辑正确。要用参数最多的接口验证,不要用参数最少的。
  3. 找Swagger先。很多平台的主文档写得一塌糊涂,但Swagger定义是机器生成的,反而更准确。
  4. 打印签名字符串。不要只看最终的MD5值,把签名前的原始字符串打出来逐字符比对。
  5. 特别注意整数和字符串的区别。Swagger里标注int32的字段,在签名字符串里可能需要用"0"而不是0。类型不匹配就是签名不对。

为什么这个坑值得写一篇文章?

因为这是一类极其常见但很少被讨论的问题。

大厂的API文档(AWS、Google、Stripe)写得非常详细,签名算法有完整的示例和SDK。但大量的中小平台——特别是国内的安全产品、CDN服务、云主机厂商——文档质量参差不齐。签名部分往往就一句话:"使用MD5签名",具体怎么拼,不说。

而签名验证又是API安全的基础。搞不定签名,后面所有业务逻辑都无从谈起。

所以下次你遇到"签名错误"的时候,别急着怀疑自己的代码。先问三个问题:

  1. 密钥是追加在末尾,还是参与排序?
  2. 有没有Swagger/OpenAPI文档可以参考?
  3. 你验证签名用的是参数最多的接口吗?

这三个问题能帮你省掉90%的排查时间。


本文基于小火龙实验室的真实踩坑经历。我们在对接DNS管理API时被签名算法折腾了半天,最终通过Swagger文档找到了答案。希望这篇文章能帮到同样在和第三方API搏斗的你。