问题:信任一个请求

当服务器收到一个 API 请求时,它如何知道调用者确实是其所声称之人,以及没有人在传输中改动了请求?随每个请求发送一个共享秘密会奏效,直到那个秘密被记入日志、被缓存或被泄露。 请求签名在从不传输秘密的情况下解决此问题:调用者通过用秘密为请求签名来证明自己持有它,而服务器验证签名。

签名如何工作

双方共享一个秘密密钥。要为请求签名,客户端:

  1. 从请求中那些不可改变的部分构建一个规范字符串,通常是 HTTP 方法、路径、关键头部、一个时间戳,以及主体的一个哈希。“规范”意味着双方以完全相同、约定好的方式将其拼装,从而对相同的输入进行计算。
  2. 计算 HMAC-SHA256(规范字符串, 秘密) 并将其附在请求上,通常放在像 AuthorizationX-Signature 这样的头部里。

服务器持有相同的秘密,从它收到的请求重建规范字符串,并自己计算 HMAC。如果它的值与头部中的相符,两件事便一举得证:调用者持有秘密(真实性),且签名所覆盖的内容均未被改动(完整性)。秘密本身从不传输。

一个真实的例子:AWS Signature Version 4

亚马逊的 SigV4 是最广为人知的实例。每个请求都用 HMAC-SHA256 在一个规范请求上签名,使用一个由秘密访问密钥、日期、区域和服务派生而来的签名密钥。服务器重新派生密钥并重新计算签名;不一致即被拒绝。同一套模式,规范化、HMAC、比较,是无数其他 API 认证方案的底层。

Webhook:同一思路,反向

Webhook 颠倒了方向。一个提供方(一个支付处理商、一个 Git 主机)调用你的端点以通知你某个事件,并用你们双方共享的秘密为有效载荷签名,在头部中发送 HMAC。你的端点在收到的主体上重新计算 HMAC 并比较,这就是你拒绝任何不持有秘密之人发来的伪造调用的方式。务必使用恒定时间检查来比较,以避免通过时间泄露信息。

重放保护

攻击者捕获的一个有效的已签名请求可能被原样重放。标准的防御是在签名数据中包含一个时间戳并拒绝短窗口之外的请求,以及可选地包含一个 nonce(服务器短暂记住的一次性值),使同一请求不能被接受两次。签名证明真实性;时间戳和 nonce 防止一个陈旧而真实的请求被重用。

HMAC 工具 在你提供的一条消息和一个密钥上,计算位于这些方案每一个核心的 HMAC-SHA256(以及 -384/512),全部在你的浏览器中。