3、绑定方案设计:身份与密钥的绑定逻辑、绑定令牌的结构设计、防篡改与防重放机制
好,咱们进入正题。前面两章我们把身份认证和密钥体系拆开讲了,这一章要做的,就是把它们「绑在一起」。
说白了,绑定方案就是回答一个问题:你怎么证明这把密钥属于这个用户? 我见过不少系统,密钥和用户信息是分开存的,结果密钥被换掉都没人知道。嗯,这章我们就来解决这个核心问题。
3.1 绑定逻辑:谁跟谁绑,怎么绑?
先理清概念。身份与密钥的绑定,本质上是在用户身份标识和密钥材料之间建立一条不可抵赖的关联关系。
我个人习惯把绑定逻辑分为三层:
- 身份层:用户ID、手机号、邮箱等唯一标识
- 密钥层:公钥指纹、密钥ID、证书序列号等密钥标识
- 绑定层:签名、时间戳、绑定策略等关联证据
这三层缺一不可。我在项目中遇到过一种情况:只存了用户ID和公钥的对应关系,但没有签名验证。结果攻击者通过SQL注入把公钥换成了自己的,后续所有认证都失效了。你说这多可怕?
所以,绑定逻辑的核心是:绑定关系本身必须被签名保护。也就是说,绑定记录里要包含用户身份、密钥信息、绑定时间,然后用一个可信的签名密钥(比如服务端私钥)对整个记录签名。
绑定关系公式
绑定记录 = { 用户身份, 密钥标识, 绑定时间, 绑定策略 } + 服务端签名
验证时:验签通过 → 绑定关系可信;验签失败 → 绑定关系无效。
3.2 绑定令牌的结构设计
绑定令牌,就是承载绑定关系的数据结构。它要能在客户端和服务端之间安全传递,还要支持离线验证。
我推荐使用类似JWT的结构,但字段要定制。下面是我在实战中打磨出来的结构:
{
"typ": "binding+token", // 令牌类型
"alg": "ES256", // 签名算法
"kid": "sk-2024-001" // 签名密钥标识
}
.
{
"sub": "user_12345", // 用户身份
"key_fp": "SHA256:abc...", // 绑定密钥的指纹
"key_type": "ecdsa_p256", // 密钥类型
"iat": 1700000000, // 绑定时间
"exp": 1700086400, // 过期时间
"policy": { // 绑定策略
"max_devices": 5,
"require_biometric": true,
"allowed_uses": ["auth", "sign"]
},
"jti": "uuid-v4" // 令牌唯一ID,防重放
}
.
[签名]
这个结构有几个关键点:
- key_fp:不是存公钥原文,而是存指纹。这样即使令牌泄露,攻击者也拿不到原始密钥。
- policy:绑定策略,控制这把密钥能干什么。我见过有些系统把所有密钥一视同仁,结果签名密钥被拿去加密,出了大问题。
- jti:令牌唯一ID,配合服务端记录,可以防止重放攻击。
我的经验:绑定令牌的过期时间不要设太长。我一般设24小时,然后支持刷新。这样即使令牌泄露,影响范围也有限。
3.3 防篡改机制
防篡改,说白了就是让攻击者改不了绑定关系。这里我用了三层防护:
- 签名保护:整个令牌用服务端私钥签名,改任何字段都会导致验签失败。
- 哈希链:在绑定记录里包含上一个绑定记录的哈希值,形成链式结构。这样即使攻击者改了当前记录,也要改前面所有记录。
- 服务端校验:每次使用绑定令牌时,服务端重新验签,并检查令牌是否在吊销列表中。
我曾经遇到过一个案例:攻击者通过中间人攻击修改了绑定令牌的policy字段,把"allowed_uses"从["auth"]改成了["auth", "admin"]。还好我们有签名保护,服务端验签时直接拒绝了。你想想看,如果没有签名,后果不堪设想。
注意:签名密钥本身的安全是防篡改的基石。如果签名密钥泄露,所有绑定关系都不可信。建议使用HSM或密钥管理服务来保护签名密钥。
3.4 防重放机制
防重放,就是防止攻击者把截获的绑定令牌重复使用。这里我用了三种策略:
| 策略 | 原理 | 优缺点 |
|---|---|---|
| 时间戳 | 令牌携带iat和exp,服务端检查时间窗口 | 简单,但依赖时钟同步 |
| 随机数 | 令牌携带jti,服务端记录已使用的jti | 安全,但需要维护状态 |
| 递增序列号 | 每个令牌携带递增的序列号,服务端检查单调性 | 高效,但需要持久化序列号 |
我个人习惯组合使用时间戳和随机数。时间戳做第一道过滤,随机数做精确去重。这样既保证了效率,又兼顾了安全。
举个例子:假设攻击者截获了一个绑定令牌,在1秒内重放。时间戳检查会通过(因为还在有效期内),但jti检查会发现这个jti已经被使用过,直接拒绝。嗯,这就是双重保险。
3.5 整体绑定流程
下面这张图展示了完整的绑定流程:
这个流程看起来简单,但每个环节都有坑。我重点说几个:
- 密钥生成必须在客户端:私钥永远不出设备,服务端只存公钥指纹。
- 身份验证要严格:绑定前必须确认用户身份,我一般用多因素认证。
- 令牌存储要安全:客户端用Keychain/Keystore存储,服务端用加密数据库。
避坑指南
我曾经在一个项目中,把绑定令牌直接存在SharedPreferences里。结果测试时发现,root过的设备可以随便读。后来改成了用Android Keystore加密存储,才解决了问题。
记住:绑定令牌的安全级别,决定了整个认证体系的安全级别。
3.6 小结
这一章我们讲了绑定方案的核心设计。说白了,就是三件事:
- 绑定逻辑:身份和密钥的关联关系必须签名保护
- 令牌结构:包含身份、密钥指纹、策略、防重放字段
- 安全机制:签名防篡改,时间戳+随机数防重放