什么是API签名?

前言

本文谈谈 【API 签名】 目标是看完之后,需要能用自己的语言说出【什么是 API 签名】

正文

API 签名(API Signature)是一种用于身份验证(Authentication) 和保证数据完整性(Data Integrity) 的安全机制。其核心是客户端使用密钥(Secret Key) ,通过密码学哈希运算【注意,这里是一种哈希】对请求的特定要素进行计算,生成一个唯一的签名(Signature)

服务器通过验证此签名来确认请求者的身份【合法】且传输过程中数据【未被篡改】。

常用技术搭配:

  1. HMAC (Hash-based Message Authentication Code,如 HMAC-SHA256):最常用的签名计算算法,运算速度快,安全性高。之前有写过文章,不了解 HMAC 的小伙伴可以去康康。
  2. 访问密钥对(Access Key Pair) :其中一个是Access Key ID (公开,用于标识身份),另一个是 Secret Key(机密,用于计算签名,绝不通过网络传输)。注意,不是非对称加密中的公钥和私钥!!!
  3. 时间戳(Timestamp):核心防重放机制,通常为 UTC 时间戳(Unix Epoch Time)。
  4. Nonce:一次性的随机数(Number used once),作为补充防重放机制,确保请求的唯一性。

补充内容:啥是重放攻击?🧐

重放攻击(Replay Attack)是一种网络攻击手段。

攻击者通过窃听或拦截客户端与服务器之间的正常通信,获取到一个【有效的】、【带有认证信息】(如密码、Token 或 API 签名)的数据请求包。然后,攻击者并不需要破解这个认证信息的内容,而是直接将它【原封不动地】、在稍后某个时间【重复发送】给服务器。

由于该请求包含合法的认证凭证,服务器无法区分这是来自真实客户的请求还是恶意重放的请求,从而被欺骗执行重复的操作,例如再次转账、重复下单或篡改数据。
补充内容:如何解决重放攻击?

为了防止重放攻击,API 签名通常会结合时间戳(Timestamp)Nonce(一次性随机数)等机制。

服务器会在接收到请求时,检查请求中的时间戳是否在合理的时间窗口内(如 10 分钟),并验证 Nonce 是否之前未被使用过。如果校验失败,服务器会拒绝处理该请求,认为它可能是一个重放攻击。

P.S. 后面还会提到时间戳防止重放攻击相关的内容 👀


API 签名的流程

补充说明:这边举的例子是属于比较【严格】的签名流程了。我目前见到的 API 签名流程没有这么复杂,仅对请求体进行签名操作。

第一阶段:客户端构造签名并发起请求

  1. 构建标准请求(Canonical Request)

    • 客户端收集 HTTP 请求的必需元素,包括:HTTP 方法(如 GET、POST)、请求的 API 端点(Endpoint)、查询字符串参数(Query String)、需要参与签名的特定头(Headers,如HostContent-Type)、以及请求体(Body,如果是 POST 或 PUT 请求)。
    • 将这些元素按照 API 提供商定义的特定规则进行**规范化(Canonicalization)**处理(例如:将参数按字母序排序、进行标准的 URI 编码、用换行符连接不同部分)。此步骤至关重要,确保了【服务器】和【客户端】对"同一个请求"的【抽象表示】是完全一致的。
  2. 创建待签字符串(String to Sign)

    • 将规范化后的请求与其他元数据进行组合,形成一个待签名的最终字符串。其通用格式通常包含:
      • 使用的签名算法(如AWS4-HMAC-SHA256)。
      • 请求的时间戳。
      • 其他凭证信息(如日期、区域、服务)。
      • 规范化请求的哈希值。
    • 示例格式:{算法}\n{时间戳}\n{credential-scope}\n{hashed-canonical-request}
  3. 计算签名(Calculate Signature)

    • 使用分配的 Secret Key 【发起方和接收方有共同的 Secret Key】,通过指定的加密算法(如HMAC-SHA256)对待签字符串进行加密计算,生成一个二进制的签名摘要,通常再将其转换为十六进制(Hexadecimal)字符串形式。
  4. 组装并发送请求(Form and Send Request)

    • 将生成的签名以及必要的验证信息(如Access Key ID【注意,Access Key ID 就是用于说明发起方身份的东西】、时间戳、算法指示)添加到原始 HTTP 请求中。添加方式通常为:
      • 通过特殊的 HTTP 头(如 Authorization Header)。
      • 或作为查询字符串参数(如 &signature=...&timestamp=...&accessKeyId=...)。
    • 最终将完整的请求发送至服务器。

第二阶段:服务端接收请求并验证

  1. 初步检查与信息提取

    • 服务器接收到请求后,首先从请求头或查询参数中提取出Access Key ID【用于判断你是谁】、时间戳(Timestamp)、客户端签名(Client Signature)等信息。
  2. 时效性验证(防重放攻击关键步骤)

    • 服务器系统获取当前时间,并与请求中的时间戳进行比较。
    • 计算两者时间差的绝对值。如果该差值超过了预设的有效时间窗口(Validity Window) (例如 15 分钟),服务器立即判定该请求无效并拒绝,返回错误(如403 Forbidden419 Authentication Timeout)。此步骤有效防御了重放攻击(Replay Attack)。
  3. 获取对端密钥

    • 根据提取出的Access Key ID,服务器在自身的 credential 数据库或缓存中查找对应的 Secret Key。如果找不到,则身份验证失败。
  4. 重构与计算签名(服务端)

    • 服务器使用与客户端完全相同的规则 ,从接收到的原始请求中提取数据,重构出规范化请求 ,进而生成与服务端一致的待签字符串,也就是和发起方一样,重新抽象化此次请求,确保请求的任何一个细节都是与预期一致的。
    • 使用查找到的 Secret Key 和相同的加密算法,对该待签字符串进行计算,得到服务端签名(Server-Side Signature)
  5. 签名比对与最终验证

    • 将计算得到的服务端签名 与客户端传来的客户端签名进行安全的比对(通常采用【恒定时间比较算法】,以防止【计时攻击 ------ 后面我会补充计时攻击的知识】)。
    • 只有满足以下两个条件,验证才算通过
      • 条件一:时间戳在有效期内。
      • 条件二:两个签名完全一致。
    • 签名一致证明了请求者的身份合法(拥有正确的 Secret Key)且请求内容在传输过程中未被任何方式篡改。

补充知识:什么是【计时攻击】?

以 API 签名验证为例,一个不安全的字符串比较函数可能是这样的:它从第一个字符开始逐个比对,一旦发现不匹配就立即返回失败。那么,比较 abcde 和 accde 会比比较 abcde 和 xbcdе 花费更少的时间,因为前者在第二个字符就失败了,而后者在第一个字符就失败了。

攻击者可以利用这个微小的耗时差异,反复发送大量精心构造的请求并记录响应时间,从而像"猜数字"一样,逐个字符地破解出正确的签名。

因此,安全的系统会使用"恒定时间比较"函数(如 Python 的 secrets.compare_digest),确保无论匹配到第几个字符失败,比较操作所花费的时间都是相同的,从而彻底杜绝计时攻击。

我也是刚刚了解这一种攻击方式,说实话,这操作我已经惊呆了 🙀🙀🙀,黑客无孔不入啊 bro

  1. 处理与响应
    • 验证通过:服务器正常处理请求,并返回业务数据。
    • 验证失败:服务器立即终止处理,返回 4xx 状态码的错误响应(如 403 Forbidden)。

这时候可能会有小伙伴说:😠😠😠 我管你这的那的,给我上图!!上图!!

ok,对应的流程图如下!

text 复制代码
【客户端 (Client)】
├─ 输入: [Secret Key] + [HTTP请求要素 (Method, Path, Headers, Body, Timestamp, Nonce...)]
├─ 处理:
│   1. 规范化: 将请求要素按特定规则排序、编码,生成【规范化请求 (Canonical Request)】
│   2. 组合: 将规范化请求与其他元数据组合,生成【待签字符串 (String to Sign)】
│   3. 计算签名: Signature = HMAC-SHA256(Secret Key, String to Sign)
└─ 发送: ┌─────────────────────────────────────────────────┐
         │            原始的 HTTP 请求 (明文)               │
         │  ├─ Headers:                                   │
         │  │    ...                                      │
         │  │    Authorization: Credential={AccessKeyId}, │
         │  │                 SignedHeaders=...,          │
         │  │                 Signature={Signature}       │
         │  │    X-Timestamp: {Timestamp}                 │
         │  │    X-Nonce: {Nonce}                         │
         │  └─ ...                                        │
         │  ├─ Body: {Request Body}                       │
         └─────────────────────────────────────────────────┘
                         |
                         ↓ 【网络传输】→ (可能被窃听、篡改、重放)
                         |
【服务端 (Server)】       ↓
├─ 接收: ┌─────────────────────────────────────────────────┐
│        │            收到的 HTTP 请求                     │
│        │  ├─ Headers:                                   │
│        │  │    ...                                      │
│        │  │    Authorization: ...                       │
│        │  │    X-Timestamp: {Received_Timestamp}        │
│        │  │    X-Nonce: {Received_Nonce}                │
│        │  └─ ...                                        │
│        │  ├─ Body: {Received_Body}                      │
│        └─────────────────────────────────────────────────┘
├─ 处理:
│   1. 📛 初步检查: 提取 AccessKeyId, Received_Timestamp, Received_Nonce, Signature
│   2. ⏰ 时效验证: |Server_Time - Received_Timestamp| > TimeWindow? → ❌失败 (防重放)
│   3. 🔑 获取密钥: 用 AccessKeyId 查数据库,找到对应的 Secret_Key
│   4. 🛠️ 重构签名:
│       1. 按相同规则从【收到请求】生成【规范化请求_Server】
│       2. 组合生成【待签字符串_Server】
│       3. Signature_Server = HMAC-SHA256(Secret_Key, String to Sign_Server)
│   5. 🔍 安全比对: Compare(Signature_Server, Received_Signature)
├─ 验证结果:
│   ├─ ✅ 成功 (同时满足):
│   │      ├─ 时间戳在有效窗口内
│   │      └─ 签名比对完全一致
│   │          → 处理请求,返回业务数据 (200 OK)
│   │
│   └─ ❌ 失败 (任一不满足):
│          → 立即拒绝请求,返回错误 (如 403 Forbidden / 419 Timeout)
└─

最后

信息安全之路任重道远

最后来回答下开头的问题:什么是 API 签名?

API 签名是一种使用密钥(Secret Key)【这个 Secret Key 是请求方和接收方共有的!】对请求核心要素进行哈希运算,生成一个唯一"指纹"(即签名)的安全机制,服务器通过核对这个"指纹"来验证请求者的合法身份并确保传输数据未被篡改。

相关链接

相关推荐
昵称为空C4 小时前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
会豪4 小时前
Electron-Vite (一)快速构建桌面应用
前端
中微子4 小时前
React 执行阶段与渲染机制详解(基于 React 18+ 官方文档)
前端
唐某人丶4 小时前
教你如何用 JS 实现 Agent 系统(2)—— 开发 ReAct 版本的“深度搜索”
前端·人工智能·aigc
中微子4 小时前
深入剖析 useState产生的 setState的完整执行流程
前端
架构师沉默4 小时前
设计多租户 SaaS 系统,如何做到数据隔离 & 资源配额?
java·后端·架构
遂心_4 小时前
JavaScript 函数参数传递机制:一道经典面试题解析
前端·javascript
小徐_23334 小时前
uni-app vue3 也能使用 Echarts?Wot Starter 是这样做的!
前端·uni-app·echarts
RoyLin5 小时前
TypeScript设计模式:适配器模式
前端·后端·node.js