密码哈希与加盐:从原理到实战
核心命题:假设数据库一定会被拖走,怎么让攻击者拿到也没用?
在本仓库中的位置这是 Claude Code Vibe Coding 应用的架构分档手册 中「鉴权与支付」一节的深度展开。 任何 Tier ≥ 1 的应用,密码存储方案都要按本文执行。
一、为什么密码不能明文存?
❌ 明文存储
┌────────────────────────────────┐
│ 用户名 | 密码 │
│ albert | 123456 │ ← 拖库 = 灾难
│ zhangsan | qwerty │
└────────────────────────────────┘
一旦数据库泄露(拖库),所有用户密码瞬间暴露。更糟的是用户常在多个网站用同一密码 → 一处泄露,处处沦陷(撞库攻击)。
📌 2011 年 CSDN 拖库事件,600 万账号明文密码泄露,引爆中国互联网密码安全意识。
二、第一层防御:哈希算法
哈希 = 单向的数学函数。
hash()
"123456" ───────► e10adc3949ba59abbe56e057f20f883e
(永远是这个值,不可反推)
三大特性
| 特性 | 含义 |
|---|---|
| 确定性 | 同样输入 → 同样输出,永远成立 |
| 不可逆 | 从哈希值反推不出原密码 |
| 雪崩效应 | 输入变一点点,输出天差地别 |
雪崩效应直观感受
"123456" ──► e10adc3949ba59abbe56e057f20f883e
"123457" ──► f1887d3f9e6ee7a32fe5e76f4ab80d63
▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
完全没有规律可言
哈希到底怎么算的?(以 MD5 为例)
"123456"
│
▼ 编码成二进制
[00110001 00110010 ...]
│
▼ 填充到 512 位倍数
[512 位数据块]
│
▼ 4 个魔法初值
A=0x67452301 B=0xefcdab89 C=0x98badcfe D=0x10325476
│
▼ 64 轮"揉面":AND / OR / XOR / 左旋 / 加常数
│ 每一轮:新B = B + 左旋(A + F(B,C,D) + M[i] + K[i], s)
│
▼ 最终拼接 A|B|C|D
e10adc39 49ba59ab be56e057 f20f883e ✅
为什么不可逆? 因为大量使用了 AND / OR 这种有损运算——1 AND 0 = 0,但反过来你不知道原本是什么。
三、单纯哈希也不够:彩虹表攻击
攻击者会预先计算常见密码的哈希,做成超大对照表:
🌈 彩虹表(攻击者一次性投入几个月算好)
┌──────────────────────────────────────────────┐
│ 123456 → e10adc3949ba59abbe56e057f20f883e│
│ password → 5f4dcc3b5aa765d61d8327deb882cf99│
│ qwerty → d8578edf8458ce06fbc5bb76a58c5ca4│
│ ...(数十亿条) │
└──────────────────────────────────────────────┘
拖库 → 拿哈希 → 查表 → 瞬间还原 1000 万个密码 💀
“算一次表,破解所有人”——成本极低。
四、第二层防御:加盐(Salt)🧂
给每个用户生成随机字符串作为盐,拼到密码后再哈希:
注册流程
─────────────────────────────────────────────
用户输入: "123456"
随机生成: 盐 = "x7Kp9mQ2"
计算: hash("123456" + "x7Kp9mQ2") = a3f5b8c1...
存储:
┌──────────┬────────────┬──────────────┐
│ 用户名 │ 盐 │ 哈希 │
├──────────┼────────────┼──────────────┤
│ albert │ x7Kp9mQ2 │ a3f5b8c1... │
│ zhang │ Lp4nR8wX │ 9d2e7f4a... │ ← 同样密码,盐不同,哈希也完全不同
└──────────┴────────────┴──────────────┘
关键认知颠覆 ⚠️
盐是公开存储的,不是秘密!
那加盐有什么用?看对比:
没加盐 🌈
─────────────
攻击者:算一次彩虹表 → 破解所有用户 ✅
成本:低
加盐后 🧂
─────────────
攻击者:每个用户盐不同
→ 用户1 算一张表(几个月)
→ 用户2 盐不一样,再算一张表(几个月)
→ 1000 万用户 = 1000 万张表 ❌
成本:彩虹表攻击经济上完全不可行
盐的作用不是"保密",而是"让批量破解失去经济意义"。
五、第三层防御:慢哈希算法
加盐后,攻击者还能针对单个用户暴力破解:
拿 albert 的盐 + 哈希
↓
试遍所有密码组合 → 找到匹配
普通 SHA-256 太快——GPU 一秒能算几十亿次。
解决方案:故意把哈希算法设计得很慢。
普通 SHA-256: 每秒 1,000,000,000 次 → 试遍 8 位密码只需几小时
bcrypt (cost=12): 每秒 300 次 → 同样的破解需要几百年
Argon2: 每秒 100 次 → 还要消耗大量内存,GPU 加速无效
| 算法 | 特点 | 推荐度 |
|---|---|---|
| MD5 / SHA-1 | 已破解,禁用 | ❌ |
| SHA-256 | 太快,不适合密码 | ❌ |
| bcrypt | 经典,自带盐 | ✅ |
| scrypt | 抗 GPU/ASIC | ✅ |
| Argon2 | 2015 密码哈希竞赛冠军 | ⭐ 首选 |
对单次登录用户无感(0.1 秒),对暴力破解者来说成本提高几亿倍。
六、第四层防御:Pepper(胡椒)🌶️
如果想再加一层——
| Salt(盐) | Pepper(胡椒) | |
|---|---|---|
| 存哪里 | 数据库,和哈希一起 | 应用代码 / 环境变量 / KMS |
| 每用户 | 不同 | 全局共用一个 |
| 拖库会泄露吗 | 会 | 不会(除非服务器也被入侵) |
hash = Argon2(密码 + 盐 + Pepper)
▲
└── 不进数据库,藏在应用层
双层物理隔离:要破解必须同时攻破数据库 + 应用服务器。
落到你的真实环境Pepper 应该存在哪?参考你已经在用的两台服务器 —— 阿里云服务器代码资产盘点-2026-04-23 与 云服务器代码资产盘点-2026-04-23 推荐做法:写进各服务的
systemdEnvironment 文件 /.env并chmod 600, 绝不进 Git;如果上了阿里云 KMS,就用 KMS 凭据 API 拉取。
七、完整防御链总览
密码安全分层防御 🛡️
┌─────────────────────────────────────────┐
│ │
│ ┌──────────────────────────────────┐ │
│ │ ❌ 明文 │ │ 拖库 = 死
│ └──────────────────────────────────┘ │
│ ⬇ │
│ ┌──────────────────────────────────┐ │
│ │ 🔒 哈希 防明文裸奔 │ │ 防彩虹表?❌
│ └──────────────────────────────────┘ │
│ ⬇ │
│ ┌──────────────────────────────────┐ │
│ │ 🔒 + 🧂 加盐 防彩虹表 │ │ 防暴力?⚠️
│ └──────────────────────────────────┘ │
│ ⬇ │
│ ┌──────────────────────────────────┐ │
│ │ 🔒 + 🧂 + 🐌 慢算法 防暴力破解 │ │ 防服务器入侵?⚠️
│ └──────────────────────────────────┘ │
│ ⬇ │
│ ┌──────────────────────────────────┐ │
│ │ 🔒+🧂+🐌+🌶️ Pepper 分层隔离 │ │ ✅ 现代最佳实践
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
注册流程
用户密码 ──► 生成随机盐 ──► Argon2(密码 + 盐 + Pepper) ──► 入库
┌────────────────┐
│ 用户 | 盐 | 哈希 │
└────────────────┘
登录流程
用户输入 ──► 查出该用户的盐 ──► Argon2(密码 + 盐 + Pepper) ──► 比对哈希
↓
相等 → 登录成功
不等 → 拒绝
八、相关攻击术语
| 术语 | 含义 |
|---|---|
| 拖库 | 攻击者把整个数据库下载走 |
| 洗库 | 在拖到的数据中筛选高价值信息(VIP、管理员等) |
| 撞库 | 用 A 网站泄露的密码去 B/C/D 网站尝试登录 |
| 彩虹表 | 预先计算的"密码 → 哈希"对照表 |
| 碰撞攻击 | 找到两个不同输入产生同一哈希值(MD5 已被攻破) |
九、给开发者的实践清单
- 永远不要自己实现密码哈希算法
- 使用成熟库:Python
passlib/ Nodebcrypt/ Rustargon2crate - 首选 Argon2id,备选 bcrypt(cost ≥ 12)
- 盐至少 16 字节,密码学安全的随机数生成器生成(不要用
Math.random()) - Pepper 存在环境变量或 KMS,永不进代码仓库
- 配套措施:登录失败次数限制、2FA、异常登录告警
在你已有项目里落地给Hermes Agent装上Open WebUI:从零到一的完整实践 中如果开放了用户登录, 建议把默认 Open WebUI 的密码哈希策略升级为 Argon2id + Pepper。 而 Claude Code Vibe Coding 应用的架构分档手册 的 Tier 0 → Tier 1 升级时, Auth.js / Clerk 默认已用 bcrypt,如果走自管 session 就要手动选算法。
十、给用户的自保清单
- 不在多个网站用同一密码(用 Bitwarden / KeePass 等密码管理器)
- 重要账户开启 2FA / TOTP(Google Authenticator、Authy)
- 定期上 haveibeenpwned.com 查邮箱是否在已知泄露库
- 长密码 > 复杂密码(
correct horse battery staple>P@ssw0rd!)
移动端体验小贴士wiki-iphone-pwa-使用指南-2026-05-02 里提到的 PWA 形态, 配合 iOS Keychain / Face ID 可以做到长密码 + 一键登录两不误, 是用户侧自保清单的最佳工程实现。
一句话总结
哈希防明文裸奔,加盐防彩虹表,慢算法防暴力破解,Pepper 防数据库单点失陷。 现代密码学不追求"绝对安全",而是让攻击成本翻几个数量级,最终让攻击者放弃。
🔗 本仓库相关笔记
强相关(同一架构链路)
- Claude Code Vibe Coding 应用的架构分档手册 —— 鉴权与支付一节的纵向展开
- 阿里云服务器代码资产盘点-2026-04-23 —— Pepper / KMS 实际落地的服务器
- 云服务器代码资产盘点-2026-04-23 —— 同上,多机部署时同步 Pepper
中等相关(应用层接入)
- 给Hermes Agent装上Open WebUI:从零到一的完整实践 —— Web 应用用户登录系统
- wiki-iphone-pwa-使用指南-2026-05-02 —— 移动端密码体验与 Face ID
知识体系入口
- 从零搭建Obsidian全栈知识系统 —— 这条笔记在领域库的位置
- Obsidian核心用法指南 —— 如何用反向链接组织安全知识
占位(待补建的笔记)
- 数据库安全
- Web 安全基础
- OWASP Top 10
- 2FA 与 TOTP 原理
- KMS 密钥管理服务
%% 整理日期: 2026-05-05 · 来源: Downloads/网站密码加盐的原理.md,已嵌入领域库双向链接 %%