存储用户密码,第一条铁律是"不能存明文",第二条容易被忽略的铁律是"也不能直接用 MD5/SHA 这类快速哈希"。这篇讲清楚为什么,以及 bcrypt、scrypt、PBKDF2 这三种"专为密码设计"的算法该怎么选。
MD5、SHA-256 这类哈希算法设计目标是"快"——校验文件完整性、做缓存 Key,都希望瞬间算完。但这个"快"用在密码存储上恰恰是灾难:
也就是说,哈希算法本身没有错,错在"太快"这个特性完全不适合密码场景——密码哈希需要的恰恰是"故意算得慢"。
给每个用户的密码加一个随机的"盐值"(salt),再一起哈希:
hash = Hash(password + salt)
盐值随哈希结果一起存储(不需要保密)。它解决的问题很具体:防止彩虹表攻击——因为每个用户的盐不同,攻击者没法用同一张预计算表打穿所有账号,必须针对每个盐值单独重新计算。
但要注意:加盐不能防止针对单个账号的暴力破解,如果密码本身很弱(比如 123456),加盐也挡不住有针对性的枚举。真正防暴力破解靠的是下面的"故意变慢"。
bcrypt/scrypt/PBKDF2 都有一个可调的"强度参数"(cost factor / iterations / rounds),故意让每次哈希计算耗时几十到几百毫秒。对正常登录来说,这点延迟用户完全无感;但对需要尝试几十亿次密码的攻击者来说,破解时间会被拉长几个数量级。
这个参数设计成可调,是为了应对硬件性能提升——过几年计算机更快了,把 cost factor 调高,继续保持"破解要花很长时间"的效果。
| bcrypt | scrypt | PBKDF2 | |
|---|---|---|---|
| 提出时间 | 1999 | 2009 | 2000(RFC 2898) |
| 核心思路 | 基于 Blowfish 加密算法改造,固定小内存 | 故意占用大量内存,抵抗 GPU/ASIC | 多轮迭代哈希,可搭配任意哈希函数 |
| 抗 GPU 并行破解 | 一般 | 强(内存密集,GPU 显存有限) | 一般 |
| 是否 NIST/官方标准 | 否(事实标准) | 否(事实标准) | 是(NIST SP 800-132 推荐) |
| 典型场景 | Web 应用用户密码存储的事实标准 | 需要更强抗 GPU 能力的场景(如加密货币钱包) | 需要符合合规/审计要求的系统 |
三者的共同点:都内置了加盐机制,都支持调整强度参数,都比直接用 MD5/SHA 安全得多。三者之外还有更新的 Argon2(2015 年密码哈希竞赛冠军),如果平台支持,是目前公认更优的选择,但 bcrypt/scrypt/PBKDF2 依然是绝大多数系统里的主流选项。
三者都远好于"裸哈希",选择时更多是权衡"生态成熟度 vs 合规要求 vs 抗硬件破解能力",不是"安全 vs 不安全"的区别。
// Node.js,bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash('my-password', 12); // 12 是 cost factor
const ok = await bcrypt.compare('my-password', hash);
# Python,PBKDF2(标准库自带)
import hashlib, os
salt = os.urandom(16)
hash = hashlib.pbkdf2_hmac('sha256', b'my-password', salt, 100000)
在线工具:Bcrypt加密校验 · scrypt密码生成校验 · PBKDF2密码派生