反馈

密码为什么要加盐哈希:bcrypt / scrypt / PBKDF2 怎么选

存储用户密码,第一条铁律是"不能存明文",第二条容易被忽略的铁律是"也不能直接用 MD5/SHA 这类快速哈希"。这篇讲清楚为什么,以及 bcrypt、scrypt、PBKDF2 这三种"专为密码设计"的算法该怎么选。


目录

  1. 为什么不能直接哈希密码
  2. 加盐(Salt)解决了什么问题
  3. 故意变慢:cost factor 的意义
  4. 三种算法对比
  5. 该怎么选
  6. 代码示例

1. 为什么不能直接哈希密码

MD5、SHA-256 这类哈希算法设计目标是"快"——校验文件完整性、做缓存 Key,都希望瞬间算完。但这个"快"用在密码存储上恰恰是灾难:

  • 现代 GPU 每秒能计算数十亿次 MD5/SHA-256
  • 攻击者拿到泄露的哈希值数据库后,暴力枚举常见密码组合,几小时内就能破解一大批弱密码
  • 彩虹表(Rainbow Table):提前算好海量"密码→哈希值"的对照表,拿到哈希值直接查表,不用实时计算

也就是说,哈希算法本身没有错,错在"太快"这个特性完全不适合密码场景——密码哈希需要的恰恰是"故意算得慢"。

2. 加盐(Salt)解决了什么问题

给每个用户的密码加一个随机的"盐值"(salt),再一起哈希:

hash = Hash(password + salt)

盐值随哈希结果一起存储(不需要保密)。它解决的问题很具体:防止彩虹表攻击——因为每个用户的盐不同,攻击者没法用同一张预计算表打穿所有账号,必须针对每个盐值单独重新计算。

但要注意:加盐不能防止针对单个账号的暴力破解,如果密码本身很弱(比如 123456),加盐也挡不住有针对性的枚举。真正防暴力破解靠的是下面的"故意变慢"。

3. 故意变慢:cost factor 的意义

bcrypt/scrypt/PBKDF2 都有一个可调的"强度参数"(cost factor / iterations / rounds),故意让每次哈希计算耗时几十到几百毫秒。对正常登录来说,这点延迟用户完全无感;但对需要尝试几十亿次密码的攻击者来说,破解时间会被拉长几个数量级。

这个参数设计成可调,是为了应对硬件性能提升——过几年计算机更快了,把 cost factor 调高,继续保持"破解要花很长时间"的效果。

4. 三种算法对比

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 依然是绝大多数系统里的主流选项。

5. 该怎么选

  • 一般 Web 应用、不确定选什么:选 bcrypt,生态最成熟,各语言库支持都很好,是事实上的默认选项
  • 需要通过金融/政务等合规审计,要求"标准算法":选 PBKDF2,因为它是 NIST 正式收录的标准
  • 对抗 GPU/专用硬件破解要求更高(如加密货币、高价值账户):选 scrypt,它的内存密集特性让专用破解硬件的成本优势大幅降低

三者都远好于"裸哈希",选择时更多是权衡"生态成熟度 vs 合规要求 vs 抗硬件破解能力",不是"安全 vs 不安全"的区别。

6. 代码示例

// 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密码派生