bcrypt 与 Argon2:如何选择密码哈希参数
bcrypt 与 Argon2 的比较:对比安全特性、真实性能成本,以及如何为现代 Web 后端选择安全的参数。

密码哈希要解决的是什么问题
密码哈希允许后端在不直接存储明文密码的情况下保存认证信息。用户注册时,服务器把密码放进一个单向函数并保存结果(哈希)。登录时,把用户输入的密码哈希后与存储值比较。
哈希不是加密。它无法被解密。正是这种单向性让哈希适合用于密码存储。
那为什么不使用像 SHA-256 这样的普通快速哈希?因为“快”正是攻击者想要的。如果数据库被窃取,攻击者不会一次一次地尝试登录。他们会离线用窃取的哈希表批量尝试猜测,用硬件尽可能快地推送猜测。使用 GPU 时,快速哈希的测试速度可以非常惊人。即便使用唯一盐,快速哈希仍然容易被暴力破解。
现实的失败场景是这样的:一个小型 web 应用泄露了用户表,攻击者获得了邮箱和密码哈希。如果这些哈希使用的是快速函数,常见密码和小变体会很快被破解。随后攻击者在其他网站进行凭证填充(credential stuffing),或者用这些密码访问你应用内更高权限的功能。
一个好的密码哈希能让猜测变得昂贵。目标不是“不可破解”,而是“慢到不值得去破解”。
一个好的密码哈希设置应该:
- 是单向的(可验证,不可逆)
- 每次猜测足够慢
- 对并行硬件(尤其是 GPU)代价高
- 仍然快到真实登录不会明显变慢
- 可调整,以便随着时间提高成本
gzip 和 Argon2,一分钟概览
比较 bcrypt vs Argon2 时,你是在决定泄露后如何放慢密码猜测速度。
bcrypt 是更老且广泛支持的选项。它设计为在 CPU 上耗时,且只有一个主要调节钮:cost 因子。它也“无趣”,这是优点:库中到处有实现,部署容易,行为可预测。
Argon2 更新一些,设计为内存难度(memory-hard)。它可以使每次密码猜测占用大量 RAM,而不仅仅是 CPU。这点重要,因为攻击者常通过在 GPU 或专用硬件上并行运行大量猜测来取胜。内存在那种并行规模下更难扩展且更昂贵。
Argon2 有三个变体:
- Argon2i:强调对某些侧信道攻击的抵抗
- Argon2d:强调对 GPU 的抵抗,并考虑更多侧信道问题
- Argon2id:兼顾两者的实用混合,也是常见的密码哈希默认值
如果你的技术栈支持 Argon2id 且你能安全地调节内存,它通常是更好的现代默认。如果你需要在老系统间最大兼容,配置好足够高的 cost 因子的 bcrypt 仍然是可靠选择。
最重要的安全属性
核心问题很简单:如果攻击者窃取了密码数据库,按规模猜测密码的代价有多高?
使用 bcrypt 时,你控制成本(work factor)。更高的 cost 意味每次猜测耗时更长。这既放慢攻击者,也会放慢你的登录检查,因此需要在对攻击者痛苦但对用户可接受之间调优。
使用 Argon2id 时,你可以在时间成本之外加入 内存难度。每次猜测既需要 CPU 时间,也需要以特定模式访问内存。GPU 在纯计算密集型任务上非常快,但当每个并行猜测需要大量内存时,它们的优势就大大下降。
盐(salt) 是不可妥协的。为每个密码使用唯一、随机的盐:
- 防止预计算表在你的数据库中重用
- 确保相同密码不会在不同用户间产生相同哈希
盐不会让弱密码变强。它们主要在数据库泄露后保护你,让攻击者必须对每个用户付出实际代价。
bcrypt 的优点与限制
bcrypt 仍然被广泛使用,主要因为它到处可用且部署简单。当你需要广泛的互操作性、堆栈中加密选项有限或只想要一个简单的调节杆时,bcrypt 通常很合适。
最大的“坑”是 72 字节密码限制。bcrypt 只使用密码的前 72 字节,忽略其余部分。这会让使用长助记短语或密码管理器的人感到意外。
如果你选择 bcrypt,请明确说明密码长度的行为。要么强制最大长度(以字节计),要么在所有服务间一致地处理过长输入。关键是避免静默截断,从而改变用户认为自己的密码是什么。
bcrypt 对现代并行破解硬件的抵抗力比内存难度方案差一些。它的防御依然有效,但更多依赖于选择一个足够高的 cost 因子以保持每次猜测昂贵。
如果你在构建新系统或拥有高价值账户(付费计划、管理员角色),常见且低风险的做法是让新哈希使用 Argon2id,同时继续接受现有的 bcrypt 哈希,直到用户再次登录再迁移。
Argon2 的优势与权衡
Argon2 是为密码哈希而设计的。Argon2id 是多数团队选择的变体,因为它在抵抗 GPU 并行破解与侧信道保护之间取得平衡。
Argon2id 提供三个参数:
- 内存 (m):每次哈希运行时使用的 RAM 大小
- 时间/迭代 (t):对这段内存进行多少次遍历
- 并行度 (p):使用多少条“通道”,有利于多核 CPU
内存是主要优势。如果每次猜测都需要可观的内存,攻击者就无法在不为内存容量和带宽付费的情况下并行运行大量猜测。
缺点是运维成本:每次哈希占用更多内存会减少并发登录数,若设置过高,登录激增时可能导致排队、超时甚至内存耗尽错误。你还需要考虑滥用情形:大量并发登录尝试会成为资源问题,除非你限制工作量。
要让 Argon2id 既安全又可用,应把它当作性能特性来调优:
- 在类似生产的硬件上做基准测试
- 限制并发哈希工作(工作池上限、队列)
- 对登录尝试速率做限制并短暂锁定重复失败
- 保持各服务设置一致,避免单点弱端点成为目标
在真实 Web 后端的性能成本
在密码哈希方面,“更快更好”通常是错误目标。你希望每次猜测对攻击者而言昂贵,但真实用户的登录感觉依然流畅。
一种实用做法是在实际生产硬件上为验证设定时间预算。许多团队的目标是在 100 到 300 ms 每次哈希检查,但正确数值取决于你的流量和服务器。bcrypt 和 Argon2 的区别在于你付出的资源:bcrypt 主要消耗 CPU 时间,而 Argon2 也会占用内存。
选择目标时间,然后测量
选择一个目标哈希时间,并在类似生产的条件下测试。既要测注册/修改密码时的哈希,也要把登录作为热点路径来处理。
一个轻量的测量计划:
- 测试 1、10、50 个并发登录检查并记录 p50 和 p95 延迟
- 重复多次以减少缓存和 CPU 提升的噪音
- 单独测量数据库调用以区分哈希的真实开销
- 使用你要部署的相同容器和 CPU 限制进行测试
峰值比平均值更重要
大多数系统在高峰期失败。如果一封营销邮件把一批用户推到登录页,你的哈希设置会决定系统是否保持响应。
如果一次验证需要 250 ms,而你的服务器并行处理能力在队列前能承受 40 个请求,那么 500 次登录尝试的突发会变成数秒的等待。在这种情况下,小幅降低成本并加强速率限制,往往比把参数推到让登录端点变得脆弱更能提升整体安全性。
保持交互式登录的可预测性
并非所有密码操作都需要相同的紧迫性。保持交互式登录的成本稳定,然后把重工作放到非关键路径。常见模式包括登录时重新哈希(在成功登录后升级用户哈希)或用后台任务处理迁移和导入。
如何逐步选择参数
参数调优的目标是在不让登录变慢或使服务器不稳定的前提下提高攻击者每次猜测的成本。
-
选择一个你的栈能良好支持的算法。若 Argon2id 可用并有良好支持,通常将其作为默认。若需要广泛兼容,bcrypt 仍然可以。
-
在类似生产的硬件上为哈希设定目标时间。选择能在峰值流量下保持登录顺畅的值。
-
调整以达到该时间。bcrypt 调整 cost 因子;Argon2id 平衡内存、迭代和并行度。内存是最能影响攻击者经济学的杠杆。
-
把算法和设置与哈希一起存储。大多数标准哈希格式会嵌入这些细节。还要确保数据库字段足够长,哈希不会被截断。
-
制定升级计划并用登录时重哈希来实现。在用户登录时,如果其存储的哈希使用的设置比当前策略弱,就重新哈希并替换。
一个实用的起点
如果你需要在测量前一个基线,可以保守地开始并根据时延调整。
- 对于 bcrypt,许多团队从 cost 12 左右开始,然后根据实际测量调整。
- 对于 Argon2id,常见基线是内存在几十到几百 MB,时间成本 2 到 4,且并行度 1 到 2。
把这些当成起点而非规则。合适的设置应适配你的流量、硬件和登录高峰。
弱化密码存储的常见错误
大多数密码存储失败来自配置漏洞,而不是算法本身坏掉。
盐的错误是大问题。每个密码都需要自己的唯一盐并和哈希一起存储。重用盐或用一个全局盐会让攻击者重复利用工作并比较账户。
忽视成本也是常见问题。团队常以较低成本上线以获得更快的登录,然后从不复查。硬件提升、攻击者规模增长,你曾经合格的设置就会变得廉价。
Argon2 过度调优也很常见。把内存设置得极高在纸面上看起来很好,但在真实峰值下会导致登录变慢、请求积压或内存耗尽。
密码长度处理也很重要,尤其要注意 bcrypt 的 72 字节行为。如果允许长密码但静默截断,会造成混淆并降低安全性。
一些实用习惯可以避免大部分问题:
- 对每个密码使用唯一盐(让库生成它们)
- 做负载测试并定期复查设置
- 针对峰值流量调优 Argon2 内存,而不是只看单次登录基准
- 明确并一致地处理密码长度限制
- 在登录端点设置并发上限和监控
更安全设置的快速清单
发布和变更基础设施时,把这张短清单放在身边:
- 每个密码唯一盐,随机生成并与哈希一起存储
- 在峰值流量下仍能承受的哈希成本,在类似生产的硬件上用负载测试验证
- 把参数与哈希一起存储,以便验证旧账户并在之后提高成本
- 在线攻击控制,包括速率限制和对重复失败的短暂锁定
- 升级路径,通常采用登录时重新哈希
一个简单的合理性检查:在预发布环境做一次包含成功和失败登录的突发测试,观察端到端延迟、CPU 和内存使用情况。如果登录流程吃力,调整成本并加强速率限制。不要通过去掉盐等基本措施来“修复”。
现实示例:为小型 Web 应用调优
想象一个拥有几千用户的小型 SaaS 应用。大部分时间是平稳的,但在发新闻邮件或工作日开始时会出现短时登录高峰。这时选择就成了容量规划问题。
你选用 Argon2id 来提高离线破解成本。先在真实服务器硬件上设定一个目标验证时间(例如 100 到 250 ms),然后在监控内存的同时调参,因为内存设置会限制你能同时处理多少次登录。
一个实用的调优循环如下:
- 从适度的迭代和并行度开始
- 增加内存直到并发变得不舒服为止
- 调整迭代次数来微调时间成本
- 用模拟突发测试而不是只测单次请求来复测
如果你已有较弱设置的旧哈希,继续验证它们但悄然升级:在成功登录后用当前设置重新哈希并保存。随着时间推移,活跃用户会无感迁移到更强的哈希。
发布后,把登录当作其他关键端点一样监控:追踪延迟(p95/p99)、突发时的 CPU 和内存、失败登录峰值,以及旧哈希被替换的速度。
下一步:安全发布并持续改进
把你的策略写下来并把它当作一个会随着时间演变的设置。例如:“Argon2id,内存 X,迭代 Y,并行 Z” 或 “bcrypt cost 因子 N”,并记录选择的日期及复查周期(每 6 到 12 个月是个合理起点)。
保持升级路径,这样你就不会被旧哈希困住。登录时重新哈希简单且在大多数系统中效果良好。
强哈希很重要,但它不能替代在线滥用控制。速率限制、锁定和谨慎的密码重置流程在实际安全中同样关键。
如果你用像 AppMaster 这样的无代码平台搭建后端,值得确认其认证模块默认使用强密码哈希并且在与你将要部署的同类基础设施上调优了哈希成本。那点前期测试常常是“既安全又顺畅”与“安全但在负载下不可用”之间的分水岭。
常见问题
密码哈希允许你在不存储明文密码的情况下验证登录。你存储一个单向哈希,然后对用户输入进行哈希并比较结果;即使数据库泄露,攻击者也必须猜密码而不是直接读取它们。
加密是可逆的,依赖密钥;如果密钥被偷或管理不当,密码就能被恢复。哈希是单向的,设计上不可“解密”,因此即便是你也不能把存储值还原为原始密码。
SHA-256 之类的快速哈希对攻击者非常有利,因为他们可以离线以极高速度尝试猜测,尤其使用 GPU。密码哈希应故意变慢(最好是内存难度高),以便大规模猜测变得昂贵。
盐值是与每个密码哈希一起存储的唯一随机值。它防止相同密码产生相同哈希,并阻止攻击者重用预计算表,但单独的盐不会使弱密码变强。
如果你的技术栈对 Argon2id 支持良好并且能安全调节内存,优先选 Argon2id,因为它设计上能抵抗并行破解。需要最大兼容性或更简单的调优时,选择 bcrypt 并设置足够高的 cost 因子。
bcrypt 有 72 字节的限制:它只使用密码的前 72 个字节,忽略其余部分。为避免惊讶,请明确限制长度(以字节计)或对长输入做一致处理,避免用户遇到静默截断。
在 Argon2id 中,内存是最重要的参数,因为它限制了攻击者能并行尝试多少次而无需为 RAM 和带宽付出巨大代价。但过多内存也会影响你服务器能并行处理多少登录请求,所以应针对峰值流量调优,而不是只看单次测试。
在真实部署硬件上为验证设定一个可预测的时间,通常每次检查约 100–300 ms,然后做并发负载测试。合适的设置是在登录高峰期仍然响应迅速,同时让离线猜测成本很高。
把算法和参数与哈希一起存储,这样你可以验证旧账户并在以后提高成本。常见做法是登录时重新哈希:如果存储的哈希比当前策略弱,在成功登录后用新设置重新计算并保存。
常见失误包括缺失或重复使用盐、上线时设定过低的成本并从不复查、以及把 Argon2 内存调得过高导致登录在高峰时超时。还要注意密码长度处理(特别是 bcrypt)并对登录端点实施速率限制和短暂锁定。


