四、令牌生成策略:随机、哈希与加密,谁更安全?

生成令牌的方式,说白了就三种:随机生成、哈希生成、加密生成。每种都有它的用武之地,也各有各的坑。

我个人习惯在项目初期就把策略定死,因为后面再改,代价挺大的。今天我们就掰开了聊聊,这三种策略到底怎么选。

4.1 随机生成——最简单,也最容易被忽略

随机生成,就是直接从熵池里抓一串随机字节,然后编码成字符串。比如 UUID v4、SecureRandom 出来的 32 字节,都可以。

核心逻辑:令牌本身就是值,不是算出来的。

// 伪代码示例:随机令牌生成
byte[] randomBytes = SecureRandom.getBytes(32);
String  = Base64URL.encode(randomBytes);
// 输出类似:a3F8zT9kLp2qR7vX4mN6cB0jH5wY1sD8

我曾在一个支付项目中见过,团队直接用 Math.random() 拼字符串——结果可想而知, 很快就被撞破了。

  • 优点:实现快,性能高,不需要额外密钥。
  • 缺点:无法追溯源数据,无法验证令牌是否被篡改。

我建议:一定要用加密级随机数生成器(如 Java 的 SecureRandom、Node.js 的 crypto.randomBytes)。普通随机数,千万别碰。

4.2 哈希生成——可重复,但别想逆行

哈希生成,就是对原始标识(比如用户 ID + 时间戳)做哈希。同一输入,永远得到同一令牌。

// 伪代码:哈希令牌生成
String raw = userId + "|" + timestamp + "|" + secret;
String  = SHA256.hash(raw);
// 输出:d2e4f6a8b0c1...(固定长度)

这玩意适合什么场景?我举个例子:设备指纹。同一设备每次请求, 都该一样。哈希正好满足。

但注意,哈希是单向的。一旦你存了哈希后的 ,就再也拿不回原始数据了。而且,彩虹表攻击、碰撞攻击都是真实存在的威胁。

我曾经踩过坑:有个项目用 MD5 做令牌哈希,后来发现攻击者用彩虹表10分钟就逆出了用户ID。MD5、SHA-1,原则上别再用。

4.3 加密生成——可逆,但要管好密钥

加密生成,就是先把数据加密,再把密文当令牌用。接收方拿到令牌后,解密就能还原原始信息。

// 伪代码:加密令牌生成
String plainText = userId + "|" + expireTime;
byte[] cipherText = AES.encrypt(plainText, secretKey);
String  = Base64URL.encode(cipherText);
// 解密后得到原始数据

它比哈希多了一个好处:你能知道 里藏了什么。比如,用户 ID、权限等级、过期时间,全塞进去,服务端不用查数据库就能验证。

但代价也很明显:密钥管理是个大麻烦。密钥泄露,令牌全部作废。我见过一个团队把密钥硬编码在代码库里,还提交到了 GitHub——嗯,两天后他们就出事。

特性 随机生成 哈希生成 加密生成
可逆性 不可逆 不可逆 可逆(需密钥)
重复性 不可重复 可重复 可重复(相同明文)
性能 最快 较快 较慢
依赖 无 无密钥 需密钥管理
安全风险 碰撞概率极低 彩虹表、碰撞攻击 密钥泄露风险
适用场景 会话令牌、一次性票据 设备指纹、去重标识 有状态的令牌、授权码

4.4 一张图看懂三种策略的差异

下面这张图,我画出了三种策略的核心流程。你看一眼,基本就清楚了。

令牌生成策略对比流程图 令牌生成策略核心流程图 原始数据 / 随机种子 随机数生成器 (SecureRandom) 哈希函数 (SHA-256) 对称加密 (AES-256) 不可逆令牌 (无关联原始数据) 不可逆令牌 (可重复,不可逆行) 可逆令牌 (需密钥解密) 审计关注点: 随机源质量 / 碰撞概率 审计关注点: 哈希算法强度 / 加盐策略 审计关注点: 密钥生命周期 / 加密模式 总结:随机生成最简单但不可控,哈希生成可重复但逆行难,加密生成可逆但密钥是命门

4.5 安全比较——到底该信谁?

你可能会问,这三种策略,到底哪种最安全?

答案是:看上下文。

  • 如果令牌只用一次,用完就丢:随机生成就够了。你想想看,一个 32 字节的随机数,攻击者想猜出来,比中彩票还难。
  • 如果你需要反复验证同一身份:哈希生成更合适。但一定要加盐,不加盐等于裸奔。
  • 如果你需要在令牌里藏信息:加密生成是唯一选择。但密钥管理这块,我建议用独立的密钥管理系统,别自己手搓。

我之前审计过一个系统:他们用 AES-ECB 模式加密令牌,结果相同前缀的令牌总是生成相同的前半段——攻击者直接通过模式匹配猜出了用户等级。ECB 模式,建议永远不要用。

来说一个实在的避坑指南:

  1. 别偷懒用内置随机函数。 Unity 的 Random.Range、PHP 的 rand() 都算普通随机数,不适合令牌。
  2. 哈希令牌一定要加盐。 不加盐的话,两个相同用户会生成相同令牌,攻击者一看就知道。
  3. 加密令牌要加签名。 我用的是 AES-GCM 模式,它自带认证,能防篡改。
  4. 定期轮换密钥。 我有个习惯,三个月换一次密钥,而且旧密钥保留一个周期用于解密已有令牌。

记住一句话:随机生成防猜测,哈希生成防逆行,加密生成防偷窥。没有银弹,只有匹配场景才是好策略。