工具
指南
本页内容

2026年8月9日

bcrypt 与 SHA-256:口令存储到底该选谁

每隔几个月,就会有一批泄露的口令哈希库被公开,事后复盘和代码评审里总绕不开同一个问题:*为什么用的是 MD5?为什么是 SHA-256?不是说好的加密哈希吗?*这种困惑可以理解,因为听起来像是一组矛盾:SHA-256 是安全的哈希函数,而用安全的哈希函数存口令是不安全的。这两句话单独看都对。问题出在——口令存储根本不是「完整性」问题,而让 SHA-256 在完整性领域出类拔萃的那个特性——快——恰恰是它在这里酿成灾难的原因。

这篇文章把离线撞库的经济学讲清楚,拆解 bcrypt 哈希串里的每个字段,用真实数字展示 cost 因子如何改变攻击成本,最后给你一条今天就能用的判断规则。文中所有例子都可以交互复现:bcrypt 哈希/校验工具完全在浏览器里运行,哈希计算器则负责每组对照中「快哈希」的那一面。

威胁模型:攻击者已经拿到了你的数据库#

理解口令哈希的第一步,是接受攻击的前提。数据库被拖走之后,攻击者手里有每一条哈希的副本,可以离线穷举——满速运转,没有频率限制,没有账户锁定,没有告警——横在他和答案之间的,只有你选的哈希函数为每次猜测所索取的代价。

在这个前提下,哈希是一种经济学防御,而不是密码学防御。你做不到让恢复口令「不可能」,你能做的是把每次猜测的价格抬到「把几十亿条候选口令都试一遍」变得不划算。所有设计决策都从这个视角展开。

现在用这个视角看两位选手。

SHA-256 是为吞吐量设计的通用哈希。在撰写本文的这台普通机器上,单个 Node.js 进程 191 毫秒算完了 100,000 个 SHA-256 摘要——单个 CPU 核、解释型运行时、还没并行,约合每秒 50 万次。换到 GPU 阵列或云上的一批实例,优化实现可以做到每秒数十亿次。MD5 更快,而这正是历史上 MD5 泄露库通常几天内就全破的原因。

bcrypt 则是一门故意跟吞吐量过不去的哈希函数。它以 Blowfish 密码的密钥编排为基础,刻意让每次调用都付出真实代价,并带一个可调的 cost 因子,硬件进步时可以把代价继续抬高。同一台机器上:cost 10 一次约 60 毫秒,cost 12 约 245 毫秒,cost 14 约 956 毫秒——比 SHA-256 慢了六个数量级。对在登录框里输口令的用户来说,这点延迟无感;对拿着被盗数据库挨个试的攻击者来说,是毁灭性的。

把真正重要的对比并排放好:

算法         单次哈希(本机)      单核每秒猜测数(约)
SHA-256      ~0.002 毫秒          ~500,000(解释型 JS)
bcrypt 10    ~60 毫秒             ~17
bcrypt 12    ~245 毫秒            ~4
bcrypt 14    ~956 毫秒            ~1

一个有用的类比:做文件完整性校验,你要的是一台能给卡车称重一次就放行的地磅;做口令存储,你要的是一扇持钥匙的人也要花四分之一秒才能推开的门——而且对试图把整栋楼的钥匙挨个试一遍的人,每一次都要花四分之一秒。

「SHA-256 加盐」为什么还是不够#

常见的反驳是:*好吧,裸 SHA-256 不行,可我加盐了呀。*加盐确实解决了两个真问题——相同的口令不再产生相同的哈希,预计算表(彩虹表)彻底失效,因为攻击者必须为每个不同的盐单独重跑整套破解。如果你现在存的是无盐 SHA-256,加上每用户一盐绝对是实打实的进步。

但盐改变的是攻击者必须做什么,而不是每次猜测花多少。拿到数据库的攻击者明文可见盐(必须可见,否则你没法验证登录),把它拼在每条候选口令后面,以 GPU 满速哈希即可。盐打败的是「表的复用」,对暴力破解的吞吐量毫无办法。底层函数的速度仍然是攻击成本里的主导项——而 SHA-256 的速度是你无法调节的。

快哈希还有第二层、更隐蔽的失败模式:它引发了一场追赶不上的军备竞赛。GPU 变快时,基于 SHA-256 的方案无法应对;你已经存下的那些哈希只会一年比一年弱。口令存储需要的是「防守方能随时间上调工作量」的方案——这正是 bcrypt 的 cost 因子所提供的。

拆解一个 bcrypt 哈希#

下面是一个真实的哈希——口令 correct horse battery staple、cost 12,由本站自己的工具生成:

$2b$12$LlQgG7M9xgdiW1SIvj/aT.1leQr0RcWT/x7GSbLgwFMt.916Uxy8y

逐字段读:

  • $2b$ —— 变种标记。$2a$ 是 OpenBSD 的原始方案;$2b$ 修正了部分实现里一个冷僻的上溢 bug;$2y$/$2x$ 多见于 PHP 生态。核心算法一致,校验端通常都收。
  • 12 —— cost 因子。密钥编排迭代 2^cost 次。
  • 接下来 22 个字符 —— 盐。每次哈希随机生成,用 bcrypt 自己的 base64 字母表(./A-Za-z0-9)编码。
  • 末尾 31 个字符 —— 推导出的哈希本体。

从这种结构里能落下两条实践结论。

**盐是内建的、可携带的。**不需要单独的盐字段,校验时只需要口令和这一条字符串——cost 和盐都随身带着。把上面这串粘进工具的校验模式、输入 correct horse battery staple,判定结果是「匹配」——cost 是工具从哈希串里自己读出来的。把同一个口令再哈希一次,会得到完全不同的字符串(新盐),而它同样校验通过——这是盐在正确工作,不是结果不稳定。

**cost 是指数的,这正是全部诀窍。**每加 1,工作量翻倍。cost 4 约一毫秒;10 约 60 毫秒;12 约 245 毫秒;14 约 960 毫秒——每 +2 约等于 4 倍。你的用户每次登录尝试付一次;攻击者要为每个账户的每条候选口令付一次。1 亿条的字典对上 1 万用户的库、cost 12——这不是一个周末的活,是一个小型数据中心月的活。

还有两个局限应当知道。bcrypt 会在 72 字节处截断输入——更长的口令短语实际上被切掉了,若必须支持长输入,需要先过一个快哈希再进 bcrypt(且只按审定的成熟方案做)。另外 bcrypt 的内存占用极小,所以在 GPU 上可以充分并行——Argon2 这类现代设计加入「内存困难」正是为了把这道门槛再抬高。

选一个 cost,并长期经营它#

cost 因子不是设定一次就不管的常量。它是一个随硬件进步需要重新校准的旋钮,合适数值取决于你的用户和威胁模型:

  • 在生产硬件上实测,而不是在笔记本上照抄文章。多数从业者收敛的目标区间是:单次校验落在 100–500 毫秒。在 2026 年的典型服务器 CPU 上,大约对应 cost 12–14。
  • **掂量你在保护什么。**论坛承受得起 100 毫秒;高价值系统应该冲上限、接受一次「郑重其事」的登录。同时 cost 必须对登录洪峰诚实——500 毫秒的校验在并发登录下会按倍数放大 CPU 负载。
  • **定期上调。**cost 是指数的,硬件的红利啃得慢但从不间断。每年复核一次;上调无需迁移——每条哈希自带 cost,新登录立刻按更高的 cost 存储。
  • 面向互联网的系统永远不要低于 10。4–9 档留给测试和基准,不是生产用的。

bcrypt 工具把这个旋钮变得可以上手:滑块从 4 到 14,每次操作都显示你的机器上的真实耗时,cost 12 及以上时计算挪进后台 Worker 线程、页面不卡。花两分钟把同一个口令在 4、10、12、14 各打一遍,你就再也不会把「cost 12」当成一个抽象数字了。

顺带把现代版图说完整:Argon2id(Argon2 家族推荐的变体)是当下的最优解,新系统在条件允许时一般应选它——它增加了内存参数,让 GPU 和 ASIC 攻击的每次猜测贵得多。scrypt 是同一方向上更早的内存困难设计。bcrypt 依然是完全站得住的选择:二十多年的生产环境历练、所有主流语言都有库支持。真正的分界线画在「有没有用慢哈希」之间,而不是这几种慢哈希彼此之间。

常见问题#

那 SHA-256 是不是被攻破了?#

没有——SHA-256 在它本职岗位上依然牢不可破:文件完整性、HMAC 签名、证书、内容寻址。错误的根源是品类混淆:SHA-256 是快的一致性哈希,口令存储需要的是慢的。各干各的活:前者用哈希计算器,后者用 bcrypt 工具。

实际部署该用什么 cost?#

从 12 起步,对照真实的登录延迟和 CPU 预算验证,能升则升。检验标准是经验性的:在生产级硬件上量单次哈希的耗时,瞄准 100–500 毫秒区间。面向互联网的系统低于 cost 10,按 2026 年的标准就是防御不足。

同一个口令哈希两次,结果为什么不一样?#

因为 bcrypt 每次哈希都生成新的随机盐并嵌进输出。两个字符串长得毫无关系,却都能通过同一口令的校验。这正是该有的样子:攻击者看不出两个用户用了同一个口令,也无法跨数据库复用任何预计算工作。

存量 SHA-256(或 MD5)库怎么迁移,总不能让所有人重置密码吧?#

在登录时做惰性重哈希:旧哈希先留着;用户登录成功时,用旧方案验一次,然后立刻用 bcrypt 把刚提交的口令重新哈希、替换存量。之后再不登录的用户手上还是弱哈希——发邮件提醒,或最终强制重置——但所有活跃账户都在无感中完成了迁移。千万不要图省事直接「对旧哈希再哈希」而不加说明;干净的做法是登录时从明文重新推导。

bcrypt 的 72 字节截断对我的用户有影响吗?#

实践中极少:72 字节装得下几乎所有手工输入的口令,而且截断也不会给攻击者「帮上忙」——但一条 120 字符的口令短语确实会悄悄丢掉超出的部分。如果你支持长口令短语,要么在表单层限制长度,要么改用没有这条限制的 Argon2id。

要不要在 bcrypt 之上再加 pepper?#

pepper——一个存在数据库之外的秘密值,混进每一次哈希——对「只拿到数据库」的攻击者确实是真实的减速带;做法通常是以 pepper 为密钥先对口令做一次 HMAC,再进 bcrypt。对成熟的团队这是合理的加固层,但它带来密钥管理的义务(轮换、HSM、可用性),多数系统都会低估这部分成本。先把慢哈希和盐做对;pepper 属于进阶的可选项。

延伸阅读#

  • 用 bcrypt 哈希/校验工具在不同 cost 下真实地哈希与校验——滑块 4 到 14、校验时自动识别 cost,全部本地计算。
  • 亲眼看一下「快」有多快:把 correct horse battery staple 喂给哈希计算器——它的 SHA-256 摘要是 c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a。
  • 存储端下了这么大功夫,口令本身也得配得上:用密码生成器生成高熵口令,再用盐生成器观察盐的形态。

← 全部指南