算法几乎从不是被攻破的那一环。真正让系统沦陷的,是这些一再重演的使用错误。
读到这里,你已经握有 AES、RSA、椭圆曲线、TLS 的原理。但真实世界的密码学事故,几乎没有一起是"AES 被破解了"。它们全都栽在接缝上——第 1 章那条暗线的最终兑现。这一章是事故档案馆,也是一份让你在 code review 里救命的清单。
头号常客。开发者图方便,把 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 章),从源头消灭"随机数用错"的可能。
alg: none:让验证器自己缴械JWT(JSON Web Token)广泛用于登录态。它由三段组成:头部(含算法 alg)、载荷(用户信息)、签名。服务器靠签名确认 token 没被篡改。经典漏洞:攻击者把 alg 改成 "none",删掉签名,而一个天真的库会照着头部说的"none"去验证——即"不验证",于是伪造的 token 畅通无阻。
下面的 Demo 让你当攻击者:拿一个普通用户的合法 JWT,把载荷里的 role 改成 admin,再用 alg:none 把签名"绕过"。看两种验证器的不同反应。
| 错误 | 后果 | 正解 |
|---|---|---|
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 浓缩成一句话:密码学的强度不在算法,在使用。 你已经见过太多次这个模式——完美的 AES 毁于 ECB,完美的 ECDSA 毁于坏随机数,完美的 TLS 毁于"信任所有证书"。所以最好的密码学工程,是尽量少写密码学代码:用高层封装、用平台设施、用审计过的库、把容易错的选择从系统里删掉。你的创造力应该用在产品上,而不是重新发明一个会漏的轮子。
工程实战到此告一段落。接下来两章,我们把镜头转向一个把密码学用到极致、也用出了新花样的领域——Web3。区块链没有发明新密码学,却用你已经学过的哈希和签名,搭出了一个"无需信任任何人"的系统。这个思路,值得一看。