3、防篡改核心原理:HMAC签名、数字摘要、时间戳防重放

好,咱们进入正题。这一章讲的是防篡改的「三根柱子」——HMAC签名、数字摘要、时间戳防重放。说白了,就是怎么证明你发给我的东西没被人动过手脚,而且不是别人录下来重播的。

我在做安全刷新项目时,最头疼的不是加密算法选哪个,而是「我怎么知道这条请求是真的?」。嗯,今天就把这三个家伙掰开揉碎讲清楚。

3.1 数字摘要:数据的「指纹」

先聊数字摘要。你想想看,怎么判断一个文件有没有被改过?最简单的办法——算个哈希值。

数字摘要就是哈希函数算出来的一串固定长度的值。常见的算法有 SHA-256、SM3。输入哪怕只改一个比特,输出的摘要就完全不一样。这就是所谓的「雪崩效应」。

核心特性:

  • 单向性:从摘要反推原文,理论上不可能(除非暴力穷举)
  • 抗碰撞性:找不到两个不同的输入产生相同的摘要
  • 固定长度:不管输入多大,SHA-256 输出永远是 256 位

我曾经遇到过一个坑:有人用 MD5 做签名校验。MD5 已经被证明可以构造碰撞了,攻击者可以伪造两个内容不同但 MD5 值相同的文件。嗯,从那以后我定了个规矩——生产环境禁止使用 MD5 和 SHA-1。

3.2 HMAC签名:带密钥的「防伪标签」

光有数字摘要够吗?不够。因为摘要本身是公开可算的。攻击者篡改数据后,重新算一遍摘要附上去,你根本发现不了。

这时候就需要 HMAC(Hash-based Message Authentication Code)出场了。HMAC 说白了就是「带密钥的哈希」。只有你和服务器知道这个密钥,别人算不出来合法的签名。

// 伪代码示例:HMAC-SHA256 签名生成
signature = HMAC-SHA256(secret_key, message)

// 验证端
expected = HMAC-SHA256(secret_key, received_message)
if (signature == expected) {
    // 数据未被篡改
} else {
    // 数据被篡改或密钥不匹配
}

我的习惯:密钥不要硬编码在代码里。用密钥管理服务(KMS)或者环境变量注入。我曾经见过有人把密钥写在配置文件里,结果代码库被拖库,整个系统沦陷。

HMAC 的好处是:即使攻击者拿到了你的消息和签名,只要他不知道密钥,就无法伪造新的签名。这就是「消息认证码」的核心价值。

3.3 时间戳防重放:让「过期」的请求失效

签名能防篡改,但防不了重放攻击。什么叫重放攻击?攻击者把一条合法的请求录下来,过一会儿再发一遍。比如你转账 100 元的请求被录下来,攻击者重放 100 次,你的钱就没了。

解决方案很简单——在消息里加一个时间戳,服务器只接受一定时间窗口内的请求。

组件 作用 常见配置
时间戳(timestamp) 标记请求生成时间 Unix 毫秒时间戳
时间窗口(ttl) 允许的最大延迟 通常 60 秒 ~ 300 秒
随机数(nonce) 防止同一请求重复提交 UUID 或自增序列

注意:时间戳必须参与签名计算。否则攻击者可以篡改时间戳来绕过窗口检查。我见过一个案例:签名只签了业务数据,时间戳单独传,结果攻击者把过期请求的时间戳改成当前时间,直接绕过。

3.4 三者协同:完整的防篡改流程

现在我们把三个东西串起来。一个完整的防篡改请求长这样:

{
  "user_id": "u_10086",
  "action": "refresh_token",
  "timestamp": 1712345678000,
  "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

生成流程:

  1. 拼接消息:user_id + action + timestamp + nonce
  2. 计算 HMAC:signature = HMAC-SHA256(secret, message)
  3. 发送请求:携带所有字段和签名

验证流程:

  1. 检查时间戳是否在窗口内(比如当前时间 ± 60 秒)
  2. 检查 nonce 是否已被使用(防止重复提交)
  3. 用同样的方式计算签名,比对是否一致

避坑指南:我曾经在 nonce 上吃过亏。当时用时间戳 + 随机数做 nonce,但没做去重存储。攻击者把同一个请求重放了两次,第二次通过了。后来我改用 Redis 的 SETNX 命令,设置 nonce 的过期时间等于时间窗口,完美解决。

3.5 核心逻辑流程图

下面这张图展示了防篡改的核心逻辑。我建议你多看几遍,理解每个环节的依赖关系。

防篡改核心逻辑流程图 防篡改核心逻辑流程图 客户端请求 拼接消息体 附加时间戳+nonce 计算HMAC签名 发送请求+签名 服务端接收请求 检查时间戳窗口 检查nonce是否已用 重新计算HMAC签名 比对签名是否一致 通过 / 拒绝 网络传输(可能被篡改) 关键点 时间戳+nonce 必须参与签名

3.6 常见攻击与防御

理论讲完了,咱们看看实战中会遇到哪些攻击。我整理了一个表格,方便你对照。

攻击类型 攻击方式 防御手段
篡改攻击 修改请求体内容 HMAC 签名校验
重放攻击 重复发送同一请求 时间戳 + nonce 去重
签名伪造 尝试猜测密钥生成签名 使用强密钥(≥256位)
时间戳篡改 修改时间戳绕过窗口 时间戳参与签名计算
中间人攻击 拦截并替换请求 HTTPS + 签名双重保障

我的建议:不要自己造轮子。用现成的库,比如 Python 的 hmac 模块、Java 的 Mac 类、Go 的 crypto/hmac。自己实现签名算法容易出漏洞,我见过有人把字符串拼接顺序搞错,导致签名验证永远通不过。

好了,这一章的核心就这些。记住三个关键词:摘要保完整性、HMAC保真实性、时间戳保时效性。三者缺一不可。