密语 · CIPHER | 目录 CH.13 / 26
绝密
Phase 3 · 哈希与完整性

密码该怎么存

从"千万别存明文"到"故意让哈希慢一万倍"——数据库被拖走之后,你还剩多少防线。

阅读 ~14 分钟 前置 第 10、11 章 Demo 快哈希 vs 慢哈希破解对比

假设你的服务器数据库被拖走了。这不是"如果",迟早会发生。此刻,用户密码的存储方式,决定了这场事故是"改个密码就好"还是"几亿账号在黑市流通"。这一章讲的就是:如何存密码,才能让攻击者即使拿到整个数据库也难以还原密码。

一、层层递进的四道防线

第 0 层:存明文 —— 犯罪

直接存 password="123456"。拖库即完全沦陷,还会连累用户在其它网站的同款密码。今天任何这么做的系统都是事故待发。永远不要存明文。

第 1 层:存哈希 —— 有进步,但不够

SHA256(password) 而非密码本身。哈希不可逆(第 10 章),看起来拖库了攻击者也还原不出密码。但有两个致命弱点:

第 2 层:加盐 —— 打掉预计算

盐(salt)是给每个用户随机生成的一段数据,和密码拼在一起再哈希:SHA256(salt ‖ password),盐明文存在数据库里(它不是秘密)。

存储:( salt,   H(salt ‖ password) )

盐解决了第 1 层的两个问题:每个用户盐不同,所以相同密码也得到不同哈希;更重要的是,攻击者无法预计算彩虹表了——他不可能为每一个可能的盐都造一张几十亿行的表。盐把攻击从"一次预计算、处处查表"逼回"针对每个用户单独爆破"。

但加盐还不够

盐挡住了预计算,却挡不住在线爆破。攻击者可以针对某个用户,拿他的盐,把常见密码一个个哈希、逐个比对。而 SHA-256 在 GPU 上每秒能算百亿次——一个 8 位纯数字密码(1 亿种)不到一秒就试完。问题的根源是:SHA-256 太快了。快对于文件校验是优点,对于密码存储却是致命伤。

第 3 层:慢哈希 —— 把"快"变成"慢"

最后的洞察反直觉却极其有效:故意让密码哈希慢下来。专门的密码哈希函数把一次哈希变成成千上万次迭代,或强制消耗大量内存:

算法慢的方式说明
PBKDF2迭代哈希 N 万次最老、最广泛支持(Android 内置)。抗 GPU 一般,但胜在到处都有。
bcrypt基于 Blowfish,可调成本因子久经考验,自带盐。有 72 字节输入上限。
scrypt迭代 + 大量内存"内存硬",让 GPU/ASIC 的并行优势失效。
Argon2内存硬 + 可调并行2015 密码哈希竞赛冠军,今天的首选

把单次哈希调到耗时约 0.25 秒,对用户登录几乎无感;但对要试几十亿次的攻击者,就是把破解成本乘以几百万倍。内存硬更狠:GPU 有几千个核心却共享有限显存,Argon2 每次哈希吃掉几十 MB 内存,GPU 的并行优势瞬间瓦解。

二、亲眼看"慢"值多少钱

下面的 Demo 破解一个短密码。左边用快哈希(单次 SHA-256,模拟裸哈希存储),右边用慢哈希(多次迭代,模拟 PBKDF2)。同样的密码、同样的爆破,看两边的耗时差距——仅仅把迭代次数从 1 提到几千,破解时间就从"瞬间"变成"喝杯咖啡"。真实的 PBKDF2 是几十万次迭代,差距还要再放大百倍。

DEMO 快哈希 vs 慢哈希 暴力破解
快哈希(SHA-256 ×1)
慢哈希(迭代)
这里只有 1 万个 PIN,两边都能破,但耗时天差地别。把它想象成 GPU 面对几十亿个真实密码:快哈希几秒破完,而调好参数的 Argon2 要几个世纪。慢,就是防线。

三、给 Android 开发者的落地清单

如果你在做认证系统

密码校验发生在服务端,慢哈希也跑在服务端。首选 Argon2id,退而求其次 bcryptPBKDF2(≥ 60 万次迭代 HMAC-SHA256,按 OWASP 建议)。
② 每个用户独立随机盐(≥16 字节),和哈希一起存。
App 端几乎不该存用户密码——用 token(OAuth / JWT)。真要在设备上保护一个本地凭证,用第 20 章的 Keystore + 生物识别,而不是自己哈希。
④ 想额外加固:再叠一层服务端密钥(pepper / HMAC),让"光拖库、没拿到密钥"也无法离线爆破。

PBKDF2 其实你天天在用

密码哈希和"从密码派生密钥"是同一件事的两面。当你用密码加密一个文件、或 WPA2 Wi-Fi 用密码生成会话密钥时,底层跑的正是 PBKDF2 这类KDF(密钥派生函数)——把低熵的人类密码,通过大量迭代"拉伸"成一把高熵密钥。慢,在这里同样是特性而非缺陷。第 24 章的钱包助记词派生,用的也是同一族思想。

Phase 3 小结

哈希这一 Phase,我们从"数据指纹"出发,理解了三大安全性质与雪崩,见识了生日攻击如何腰斩抗碰撞强度,拆穿了长度扩展的陷阱并用 HMAC 修好,最后学会了用"慢"守护密码。哈希看似朴素,却是完整性、认证、密码存储的共同地基——更是接下来数字签名区块链不可或缺的零件。

但我们始终没能回答那个最古老的问题:Alice 和 Bob 从未见过面,怎么在满是窃听者的网络上凭空商定一把共享密钥?对称密码和哈希都无能为力。解开它需要一场真正的思想革命——把一把钥匙拆成"公开"和"私有"两半。下一个 Phase,公钥密码学登场。

本章要点

  • 存密码的演进:明文(禁止)→ 裸哈希(怕彩虹表)→ 加盐(打掉预计算)→ 慢哈希(抗在线爆破)。
  • 盐是每用户随机、明文存储,消除相同密码撞哈希与预计算彩虹表。
  • SHA-256 太快反成弱点;密码哈希要故意慢:PBKDF2 / bcrypt / scrypt / Argon2(首选 Argon2id)。
  • 密码校验放服务端;App 端用 token + Keystore,不自己存密码。