什么是API签名?
简介
API 签名(API Signature)是一种用于身份验证(Authentication) 和保证 数据完整性(Data Integrity) 的安全机制。其核心是客户端使用 密钥(Secret Key),通过密码学哈希运算【注意,这里是一种哈希】对请求的特定要素进行计算,生成一个唯一的 签名(Signature)。
服务器通过验证此签名来确认请求者的身份【合法】且传输过程中数据【未被篡改】。
常用技术搭配
- HMAC(Hash-based Message Authentication Code,如
HMAC-SHA256
):最常用的签名计算算法,运算速度快,安全性高。之前有写过文章,不了解 HMAC 的小伙伴可以去康康。 - 访问密钥对(Access Key Pair):其中一个是
Access Key ID
(公开,用于标识身份),另一个是Secret Key
(机密,用于计算签名,绝不通过网络传输)。注意,不是非对称加密中的公钥和私钥!!! - 时间戳(Timestamp):核心防重放机制,通常为 UTC 时间戳(Unix Epoch Time)。
- Nonce:一次性的随机数(Number used once),作为补充防重放机制,确保请求的唯一性。
补充内容:啥是重放攻击?🧐
重放攻击(Replay Attack)是一种网络攻击手段。
攻击者通过窃听或拦截客户端与服务器之间的正常通信,获取到一个【有效的】、【带有认证信息】(如密码、Token 或 API 签名)的数据请求包。然后,攻击者并不需要破解这个认证信息的内容,而是直接将它【原封不动地】、在稍后某个时间【重复发送】给服务器。
由于该请求包含合法的认证凭证,服务器无法区分这是来自真实客户的请求还是恶意重放的请求,从而被欺骗执行重复的操作,例如再次转账、重复下单或篡改数据。
补充内容:如何解决重放攻击?
为了防止重放攻击,API 签名通常会结合 时间戳(Timestamp)
和 Nonce(一次性随机数)
等机制。
服务器会在接收到请求时,检查请求中的时间戳是否在合理的时间窗口内(如 10 分钟),并验证 Nonce 是否之前未被使用过。如果校验失败,服务器会拒绝处理该请求,认为它可能是一个重放攻击。
P.S. 后面还会提到时间戳防止重放攻击相关的内容 👀
API 签名的流程
补充说明:这边举的例子是属于比较【严格】的签名流程了。我目前见到的 API 签名流程没有这么复杂,仅对请求体进行签名操作。
第一阶段:客户端构造签名并发起请求
构建标准请求(Canonical Request):
- 客户端收集 HTTP 请求的必需元素,包括:HTTP 方法(如 GET、POST)、请求的 API 端点(Endpoint)、查询字符串参数(Query String)、需要参与签名的特定头(Headers,如
Host
、Content-Type
)、以及请求体(Body,如果是 POST 或 PUT 请求)。 - 将这些元素按照 API 提供商定义的特定规则进行规范化(Canonicalization)处理(例如:将参数按字母序排序、进行标准的 URI 编码、用换行符连接不同部分)。此步骤至关重要,确保了【服务器】和【客户端】对“同一个请求”的【抽象表示】是完全一致的。
创建待签字符串(String to Sign):
将规范化后的请求与其他元数据进行组合,形成一个待签名的最终字符串。其通用格式通常包含:
- 使用的签名算法(如
AWS4-HMAC-SHA256
)。 - 请求的时间戳。
- 其他凭证信息(如日期、区域、服务)。
- 规范化请求的哈希值。
- 使用的签名算法(如
示例格式:
{算法}\n{时间戳}\n{credential-scope}\n{hashed-canonical-request}
计算签名(Calculate Signature):
- 使用分配的
Secret Key
【发起方和接收方有共同的 Secret Key】,通过指定的加密算法(如HMAC-SHA256
)对 待签字符串 进行加密计算,生成一个二进制的签名摘要,通常再将其转换为十六进制(Hexadecimal)字符串形式。
组装并发送请求(Form and Send Request):
将生成的签名以及必要的验证信息(如
Access Key ID
【注意,Access Key ID 就是用于说明发起方身份的东西】、时间戳、算法指示)添加到原始 HTTP 请求中。添加方式通常为:- 通过特殊的 HTTP 头(如
Authorization Header
)。 - 或作为查询字符串参数(如
&signature=...×tamp=...&accessKeyId=...
)。
- 通过特殊的 HTTP 头(如
最终将完整的请求发送至服务器。
第二阶段:服务端接收请求并验证
初步检查与信息提取:
- 服务器接收到请求后,首先从请求头或查询参数中提取出
Access Key ID
【用于判断你是谁】、时间戳(Timestamp)、客户端签名(Client Signature)等信息。
时效性验证(防重放攻击关键步骤):
- 服务器系统获取当前时间,并与请求中的时间戳进行比较。
- 计算两者时间差的绝对值。如果该差值超过了预设的 有效时间窗口(Validity Window)(例如 15 分钟),服务器立即判定该请求无效并拒绝,返回错误(如
403 Forbidden
或419 Authentication Timeout
)。此步骤有效防御了重放攻击(Replay Attack)。
获取对端密钥:
- 根据提取出的
Access Key ID
,服务器在自身的 credential 数据库或缓存中查找对应的Secret Key
。如果找不到,则身份验证失败。
重构与计算签名(服务端):
- 服务器使用与客户端完全相同的规则,从接收到的原始请求中提取数据,重构出规范化请求,进而生成与服务端一致的待签字符串,也就是和发起方一样,重新抽象化此次请求,确保请求的任何一个细节都是与预期一致的。
- 使用查找到的 Secret Key 和相同的加密算法,对该待签字符串进行计算,得到服务端签名(Server-Side Signature)。
签名比对与最终验证:
将计算得到的服务端签名与客户端传来的客户端签名进行安全的比对(通常采用【恒定时间比较算法】,以防止【计时攻击 —— 后面我会补充计时攻击的知识】)。
只有满足以下两个条件,验证才算通过:
- 条件一:时间戳在有效期内。
- 条件二:两个签名完全一致。
签名一致证明了请求者的身份合法(拥有正确的 Secret Key)且请求内容在传输过程中未被任何方式篡改。
补充知识:什么是【计时攻击】?
以 API 签名验证为例,一个不安全的字符串比较函数可能是这样的:它从第一个字符开始逐个比对,一旦发现不匹配就立即返回失败。那么,比较 abcde 和 accde 会比比较 abcde 和 xbcdе 花费更少的时间,因为前者在第二个字符就失败了,而后者在第一个字符就失败了。
攻击者可以利用这个微小的耗时差异,反复发送大量精心构造的请求并记录响应时间,从而像“猜数字”一样,逐个字符地破解出正确的签名。
因此,安全的系统会使用“恒定时间比较”函数(如 Python 的 secrets.compare_digest),确保无论匹配到第几个字符失败,比较操作所花费的时间都是相同的,从而彻底杜绝计时攻击。
我也是刚刚了解这一种攻击方式,说实话,这操作我已经惊呆了 🙀🙀🙀,黑客无孔不入啊 bro
处理与响应:
- 验证通过:服务器正常处理请求,并返回业务数据。
- 验证失败:服务器立即终止处理,返回
4xx
状态码的错误响应(如403 Forbidden
)。
这时候可能会有小伙伴说:😠😠😠 我管你这的那的,给我上图!!上图!!
ok,对应的流程图如下!
【客户端 (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)
└─
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
什么是 API 签名?
信息安全之路任重道远
API 签名是一种使用密钥(Secret Key)【这个 Secret Key 是请求方和接收方共有的!】对请求核心要素进行哈希运算,生成一个唯一“指纹”(即签名)的安全机制,服务器通过核对这个“指纹”来验证请求者的合法身份并确保传输数据未被篡改。
🔒什么是HMAC
初印象
HMAC 怎么读?
HMAC 的标准读法是:/ˈeɪtʃ.mæk/ (谐音:“艾奇-麦克”)
HMAC(Hash-based Message Authentication Code,基于【哈希】(注意,不是基于【加密】的!!)的消息认证码)是一种用于【验证消息完整性】和【真实性】的技术。
它结合了加密哈希函数【Cryptographic Hash Function】(如 SHA256、MD5)和一个秘密密钥(Secret Key)。
简单来说,HMAC 可以确保:
- 完整性:消息在传输过程中没有被篡改。
- 真实性:消息确实来自拥有共享秘密密钥的声称者。
- 它常用于 API 签名、JWT(JSON Web Tokens)、安全通信协议等场景。
工作流程
HMAC 的工作过程可以分为三个核心步骤:
首先,发送方将【原始消息】与一个【共享的密钥】进行混合,通过特定的哈希算法(如 SHA-256)生成一段固定长度的【认证码(HMAC 值)】。这个过程中,密钥确保了生成结果的唯一性。
接着,发送方将【原始消息】和【计算出的 HMAC 值】一起传输给接收方。
最后,接收方使用相同的密钥和哈希算法对收到的消息重新计算 HMAC 值。通过比对计算出的 HMAC 与接收到的 HMAC 是否完全一致,来验证消息在传输过程中是否被篡改以及是否来自合法的发送方。任何对消息或密钥的修改都会导致验证失败。
这时候你可能会说,我管你这的那的,给我上图!!
ok,这就上图!!
【发送方】
├─ 输入: [密钥] + [原始消息]
├─ 处理: HMAC = Hash(密钥, 消息)
└─ 发送: ┌─────────────┐
│ 原始消息 │
│ HMAC值 │
└─────────────┘
|
↓ 【传输】→ (可能被篡改)
|
【接收方】 ↓
├─ 接收: ┌─────────────┐
│ │ 收到消息 │
│ │ 收到HMAC │
│ └─────────────┘
├─ 处理: HMAC' = Hash(密钥, 收到消息)
├─ 比对: [HMAC'] vs [收到HMAC]
└─ 结果:
├─ 相同 → ✅ 验证成功 (消息完整可信)
└─ 不同 → ❌ 验证失败 (消息被篡改)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
关于 HMAC 值
疑问:所以 HMAC 值总结来说算是一种摘要?还是签名?还是哈希值?还是其他的什么?
可以这样来理解:
HMAC 值本质上是一种“带密钥的哈希值”或“加密的摘要”,其目的是为了“签名认证”。
把它拆解开来就非常清晰了:
它是一种(哈希值/摘要)
- 它的形态和普通的哈希值(如 SHA256)一样,是一段固定长度的、看起来是乱码的字符串。它确实是通过哈希函数计算出来的,具备哈希的核心特性:只要输入(消息或密钥)有丝毫改变,输出的 HMAC 值就会完全不同。
但它不是普通的哈希值,而是“带密钥的”
- 普通的哈希值(例如 MD5、SHA1)是公开的,任何人用同一个算法对同一消息计算,得到的结果都相同。这意味着别人可以篡改消息后重新计算哈希值,让你无法察觉。
- HMAC 的核心在于引入了“密钥”。没有这个密钥,就无法计算出相同的哈希结果。这使它从一种简单的完整性校验(检查数据是否出错)工具,升级为一种认证(Authentication)工具(确认数据来自谁且未被篡改)。
它的作用是“签名认证”,但不是数字签名
- 目的类似签名:HMAC 用于证明消息的来源(拥有密钥的人)和完整性。从这个目的上看,它扮演了“签名”的角色,常被称为“消息认证码”。
- 但技术上是两回事:真正的数字签名(如 RSA 签名)基于非对称加密(公钥/私钥),不需要共享密钥,还能提供不可否认性(用私钥签名后,拥有公钥的人都可以验证,但无法否认这个签名是自己做的)。
- HMAC 基于对称加密(共享密钥),双方共享同一个密钥。接收方可以伪造出一个有效的 HMAC,因此无法提供不可否认性(因为双方都能计算出同样的 HMAC,无法确定到底是哪一方生成的)。
总结与类比
特性 | 普通哈希值 (e.g., SHA256) | HMAC | 数字签名 (e.g., RSA) |
---|---|---|---|
核心目的 | 无 | 完整性 + 身份认证 | 完整性 + 身份认证 + 不可否认性 |
密钥 | 灵活,支持复杂规则 | 对称密钥(一个秘密,双方共享) | 非对称密钥(私钥签名,公钥验证) |
输出 | 哈希值/摘要 | 带密钥的哈希值(消息认证码) | 数字签名 |
类比 | 文件封条(检查是否被拆) | 蜡封+私章(确认是谁封的) | 公证处的签名盖章(具有法律效力,无法抵赖) |
所以,最准确的描述是:HMAC 值是一个用于认证目的的消息认证码,其技术实现形式是一个带密钥的哈希值。
补充:签名和摘要的区别
摘要是对消息本身计算出的、用于验证完整性的唯一指纹
签名则是使用非对称加密中的私钥对摘要进行加密后的结果,它在验证完整性的基础上,额外提供了身份认证和不可否认性。因为我如果能用你的公钥对消息解密成功,那我就可以确定你是发送方,而不是第三方,这就是不可否认性!!
啥是 HMAC?
HMAC(基于哈希的消息认证码)是一种使用加密哈希函数(如 SHA-256)和共享密钥,为消息生成一段固定长度认证码的技术。
它用来同时验证数据的完整性(未被篡改)和真实性(来自拥有正确密钥的发送方)。
接收方使用相同密钥和算法重新计算认证码并进行比对,任何不一致都意味着消息无效。
签名简介
【签名】用于验证消息来源和完整性。需配合哈希算法(如 SHA-256)和密钥对(用私钥签名,用公钥验证)。它只用于非对称加密(也就是发送方和接收方分别有自己的公钥和私钥)
签名和验签的流程【这是最重要的部分,要十分熟练】
发送方(签名):
a. 对原始消息计算哈希值(摘要)。
b. 使用发送方自己的私钥,对这个哈希值进行签名运算,生成数字签名。
c. 将【原始消息】和【数字签名】一起发送给接收方。
接收方(验签):
a. 接收后,将收到的内容分离成【原始消息】和【数字签名】两部分。
b. 使用发送方的公钥对数字签名进行【验签】运算,从而解出一个哈希值 H1。【⚠️⚠️ 注意,这边说的是验签,这个词会比“解密”更精准】
c. 最关键的一步: 使用【相同的哈希算法】,对收到的原始消息单独计算一次哈希值 H2。【⚠️⚠️ 注意,也就是双方的哈希算法是提前约定好的】
d. 将计算得到的 H2 与从签名中解出的 H1 进行比对。
如果 H1 == H2:验签成功!证明:1. 消息确实来自发送方(认证性)。2. 消息在传输中未被篡改(完整性)。
如果 H1 != H2:验签失败!证明消息要么被篡改,要么不是声称的发送方所发。