密语 · CIPHER | 目录 CH.22 / 26
绝密
Phase 5 · 工程实战

真实世界的翻车现场

算法几乎从不是被攻破的那一环。真正让系统沦陷的,是这些一再重演的使用错误。

阅读 ~15 分钟 前置 第 5–20 章 Demo 找出漏洞代码审计

读到这里,你已经握有 AES、RSA、椭圆曲线、TLS 的原理。但真实世界的密码学事故,几乎没有一起是"AES 被破解了"。它们全都栽在接缝上——第 1 章那条暗线的最终兑现。这一章是事故档案馆,也是一份让你在 code review 里救命的清单。

一、硬编码密钥:APK 里没有秘密

头号常客。开发者图方便,把 API 密钥、加密密钥、签名私钥直接写进代码或资源文件:

✗ 血泪反面教材
val secretKey = "MyS3cr3tAESKey!!"       // 逆向 10 秒就能读到
val apiToken  = "sk_live_a1b2c3d4..."    // 直接印在 APK 里

记住第 1 章的 Kerckhoffs 原则:对手默认已拿到你的全部代码。APK 是可以随便下载、反编译的(参见本站的逆向教程),字符串常量、资源文件、native 库里的密钥,用 strings、jadx、Frida 几分钟就能挖出来。GitHub 上有无数爬虫专门扫开源项目和 APK 里泄露的密钥。任何进了客户端二进制的"秘密",都不再是秘密。

正确姿势:密钥进 Keystore(第 20 章)、由服务端按需下发、或干脆让敏感操作发生在服务端。混淆(如把密钥拆成几段拼接)只是提高门槛,不改变本质。

二、坏掉的随机数:安全的地基塌了

密码学的一切都建立在"不可预测的随机数"上:密钥、IV/nonce(第 8、9 章)、ECDSA 的 k(第 18 章)、盐(第 13 章)。随机数一旦可预测,上层再强也全线崩溃。

两起教科书级事故

① Debian OpenSSL(2008):一个维护者为消除内存检查工具的警告,注释掉了一行往随机池里加熵的代码。后果:此后两年该系统生成的所有 SSH/SSL 密钥,熵只剩约 15 位——总共只有 3 万多种可能,可被瞬间枚举。全球无数密钥需要作废重生成。
② Android SecureRandom(2013):某版本 Android 的 SecureRandom 存在缺陷,未被正确初始化。多个比特币钱包 App 因此在签名时产生了重复的 ECDSA nonce k——正是第 18 章说的那个致命错误——导致私钥被人从区块链上的签名反推出来,钱包被洗劫。

教训:①永远用 SecureRandom,绝不用 Random(后者是可预测的伪随机,专为游戏/模拟设计);②别自己给随机数生成器"播种"(seed),让系统用操作系统熵源初始化;③签名优先用确定性方案(RFC 6979 / Ed25519,第 18 章),从源头消灭"随机数用错"的可能。

三、JWT 的 alg: none:让验证器自己缴械

JWT(JSON Web Token)广泛用于登录态。它由三段组成:头部(含算法 alg)、载荷(用户信息)、签名。服务器靠签名确认 token 没被篡改。经典漏洞:攻击者把 alg 改成 "none",删掉签名,而一个天真的库会照着头部说的"none"去验证——即"不验证",于是伪造的 token 畅通无阻。

下面的 Demo 让你当攻击者:拿一个普通用户的合法 JWT,把载荷里的 role 改成 admin,再用 alg:none 把签名"绕过"。看两种验证器的不同反应。

DEMO JWT alg=none 伪造
健壮的验证器会硬性要求指定算法(如只接受 HS256),从而拒绝 alg:none。永远不要让不可信的输入(token 头部)决定用什么算法来验证它自己。

四、其它高频雷区速查

错误后果正解
Cipher.getInstance("AES")默认 ECB,泄露结构(第 8 章)显式 AES/GCM/NoPadding
硬编码 IV / IV 全零确定性加密,nonce 重用(第 5、9 章)每次随机 IV,随密文存
只加密不认证(裸 CBC/CTR)可被篡改、padding oracle(第 8 章)用 AEAD(GCM)
hash(key+msg) 当 MAC长度扩展攻击(第 12 章)HMAC
MAC 用 == 比较时间侧信道常数时间比较
用 MD5/SHA-1 做安全用途碰撞可造(第 11 章)SHA-256 起步
密码直接 SHA-256 存彩虹表/GPU 秒破(第 13 章)Argon2/bcrypt + 盐
自定义 TrustManager 信任所有证书MITM 畅通(第 19 章)用默认校验,按需 pinning
自研加密算法必有隐藏弱点(第 1 章)用标准算法 + 审计过的库

五、密钥管理清单

上线前对着过一遍

□ 没有任何密钥/token 硬编码在代码、资源或 native 库里
□ 所有随机数来自 SecureRandom,没有手动 seed
□ 对称加密一律 AEAD(AES-GCM / ChaCha20-Poly1305),IV 随机且不复用
□ 长期密钥存于 Android Keystore,敏感操作绑定生物识别
□ 密码在服务端用慢哈希 + 盐;客户端不存密码
□ 签名/验签指定确定的算法,不信任 token 自报的 alg
□ 有密钥轮换与吊销方案(别指望密钥永不泄露)
□ 关键的授权、金额、配额校验发生在服务端,客户端校验只当 UX
□ 依赖库保持更新(密码学库的 CVE 要及时跟)

Phase 5 的终极心法

把这一 Phase 浓缩成一句话:密码学的强度不在算法,在使用。 你已经见过太多次这个模式——完美的 AES 毁于 ECB,完美的 ECDSA 毁于坏随机数,完美的 TLS 毁于"信任所有证书"。所以最好的密码学工程,是尽量少写密码学代码:用高层封装、用平台设施、用审计过的库、把容易错的选择从系统里删掉。你的创造力应该用在产品上,而不是重新发明一个会漏的轮子。

工程实战到此告一段落。接下来两章,我们把镜头转向一个把密码学用到极致、也用出了新花样的领域——Web3。区块链没有发明新密码学,却用你已经学过的哈希和签名,搭出了一个"无需信任任何人"的系统。这个思路,值得一看。

本章要点

  • 真实事故几乎都源于误用而非算法被破:硬编码密钥、坏随机数、alg:none、ECB、IV 复用……
  • APK 里没有秘密——密钥进 Keystore 或服务端;随机数只用 SecureRandom 且不手动 seed。
  • 验证签名/token 时由服务器固定算法,绝不信任输入自报的 alg。
  • 密码学强度在于使用而非算法;最佳工程是少写密码学代码、把易错选项删除、关键校验放服务端。