Skip to content
返回博客
安全

bcrypt vs Argon2 vs scrypt:2026 密码哈希算法对比

对比 bcrypt、Argon2id、scrypt 三种密码哈希算法,附 OWASP 2026 推荐参数、选型思路与代码示例。给应用选对哈希。

18 分钟

bcrypt vs Argon2 vs scrypt:2026 密码哈希算法对比

一句话答案:2026 年新项目用 Argon2id,参数 m=19456, t=2, p=1。这是 OWASP 密码存储速查表给出的基线,目前抗 GPU 与抗侧信道能力最好的密码哈希算法就是它。

技术栈接不进 Argon2 时(罕见,但有些嵌入式或老旧运行时确实如此),退一步用 scrypt,参数 N=2^17, r=8, p=1bcrypt 配合 cost=12 留给那些已经在用 bcrypt、又没法引入新依赖的遗留系统。如果硬性要求 FIPS-140 合规,那就 PBKDF2-HMAC-SHA-256,迭代 600,000 次

算法OWASP 2026 参数适用场景
Argon2idm=19456 KiB, t=2, p=1新项目默认选择
scryptN=2^17, r=8, p=1Argon2 不可用时
bcryptcost=12(最低 10)仅遗留系统
PBKDF2HMAC-SHA-256,600k 次迭代必须 FIPS-140 合规

下面会解释这些数字怎么来的、如何按自己的硬件调参,以及怎么在不强制用户重置密码的情况下完成迁移。基准测试需要一批强度够的测试密码,可以用随机密码生成器生成。完整的安全清单见 Web 安全最佳实践指南

密码哈希和通用哈希不是一回事

哈希函数从外面看都差不多:数据进去,定长摘要出来,无法逆推。但「给一个 4 GB 的 ISO 算哈希」和「给一个 12 字符密码算哈希」的目标完全相反。前者要尽硅片之所能地快,后者要尽登录延迟预算之所容地慢。

把这两件事混在一起做,就是数据库泄露最后变成账号失陷的常见原因。

MD5 和 SHA-256 为什么不够用

MD5、SHA-1、SHA-256 这类通用哈希是为吞吐量设计的。普通 CPU 上每秒处理几 GB 数据,GPU 上每秒能上几十 GB。做文件校验和、内容寻址很合适,拿来存密码就是灾难。

2024 年单卡 RTX 4090 跑 Hashcat 的数据:MD5 约 164 GH/sSHA-256 约 22 GH/s。一个 8 字符的小写字母数字密码(36^8 ≈ 2.8 × 10^12 种候选),单 GPU 不到一分钟就能破 MD5,SHA-256 也只要几分钟。存 sha256(password) 的库被拖出来,基本等同于明文。

加 salt 也救不了。salt 能废掉预计算彩虹表,但拖不住针对单一账号的攻击:攻击者把候选密码和泄露的 salt 拼一下再哈希就行了。

非安全用途的校验和场景,MD5 和 SHA-256 还是有用——通用哈希生成器这类工具就是干这个的。何时用哪种算法的细节见 MD5 与 SHA-256 哈希算法对比。但密码场景,需要的是一个被刻意拖慢的哈希。

现代密码哈希需要的三个特性

2026 年值得部署的密码哈希要满足三件事:

  1. 设计上就慢,工作因子可调。单次登录控制在 100–500 ms——快到用户感觉不到,慢到离线攻击者每枚举一百万次就要烧掉好几天。工作因子要做成参数,硬件升级了好往上调。
  2. 每条记录独立 salt。每个密码配一条唯一随机 salt,彩虹表失效,攻击者只能挨个账号试。现代算法会自动生成 salt 并写进输出字符串。
  3. 内存困难(memory-hard)。GPU 和 ASIC 算力强,但高带宽内存贵。一个每次哈希要吃几十 MiB 的算法,会逼着攻击者按并行度配 RAM,GPU 农场的成本曲线就崩了。

bcrypt 满足 (1) 和 (2),但不满足 (3)。scrypt 是第一个三项都占齐的算法。Argon2 在它基础上做了细化,拿下密码哈希竞赛。下面逐个看。

三种算法的架构与取舍

bcrypt:基于 Blowfish 的时间困难

bcrypt 由 Niels Provos 和 David Mazières 在 1999 年为 OpenBSD 写的。它基于 Blowfish 密码,把昂贵的密钥设置阶段(叫「EksBlowfish」)重复跑 2^cost 次。可调参数只有一个:工作因子(cost factor,也叫「log rounds」),加一翻倍。cost=10 跑 1,024 次密钥调度,cost=14 跑 16,384 次。

一个 bcrypt 哈希长这样:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
 │  │  │                      │
 │  │  │                      └─ 31 字符 base64 哈希
 │  │  └─ 22 字符 base64 salt
 │  └─ cost factor (12)
 └─ 算法标识符($2b$ = bcrypt v2)

格式自描述:verify() 从存储字符串里就能读出 cost 和 salt,不需要额外字段。

缺点也是真的。bcrypt 内存占用约 4 KiB,一张高端 GPU 能并行跑几千上万个 bcrypt 核。bcrypt 还会在 72 字节处静默截断输入——一个 100 字符的 passphrase,安全强度只等于前 72 字节。最大 cost 是 31,但超过 16 之后,普通硬件上的登录延迟就开始顶不住了。

scrypt:开了内存困难的头

scrypt 由 Colin Percival 在 2009 年为 Tarsnap 备份服务发布,2016 年成为标准 RFC 7914。它引入了内存困难(memory-hardness):算法用伪随机数据填满一大块缓冲区,再从随机位置读取,逼着任何实现都得真分配这块内存。

scrypt 有三个参数:

  • N:CPU/内存代价(必须是 2 的幂)
  • r:块大小,字节数(同时影响内存与混合轮数)
  • p:并行度(独立计算数,主要用来在不增加内存的前提下增加 CPU 时间)

内存使用约 128 × N × r 字节。OWASP 推荐的 N=2^17, r=8 对应 128 × 131072 × 8 = 134,217,728 字节,也就是每次哈希 128 MiB

scrypt 也是密钥派生函数,不光是密码哈希。加密货币钱包、全盘加密、最早的莱特币工作量证明都用过。一个项目要同时做密码存储和密钥派生时,这种双重身份用起来方便。

Argon2(id/i/d):密码哈希竞赛冠军

密码哈希竞赛(Password Hashing Competition)从 2013 年开到 2015 年,24 个候选算法围绕内存困难、抗侧信道、实现简洁三方面评比,Argon2 胜出。2021 年成为标准 RFC 9106

Argon2 有三个变体,差别在混合阶段的内存寻址方式:

  • Argon2d 用数据相关(data-dependent)寻址。对 GPU 和 ASIC 攻击抵抗最强,但会通过缓存时序侧信道泄露信息。适合加密货币工作量证明,不适合身份验证。
  • Argon2i 用数据无关(data-independent)寻址。抗侧信道,但对 GPU 时空折中攻击的抵抗稍弱。
  • Argon2id 是混合的:第一次遍历的前半段用 Argon2i 寻址(抗侧信道),其余部分用 Argon2d 寻址(抗 GPU)。RFC 9106 推荐 Argon2id 做密码哈希,OWASP 也一样。

Argon2 三个参数:

  • m:内存,单位 KiB
  • t:时间代价(对内存缓冲区的遍历次数)
  • p:并行度(并发处理的 lane 数)

Argon2id 哈希用 PHC 字符串格式,样子是:

$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

和 bcrypt 一样,所有参数都写在字符串里,verify() 不需要额外的参数表。

OWASP 2026 推荐参数

OWASP 密码存储速查表 是权威参考。下面的数字和它的当前指南一致,取值偏保守,按典型 Web 服务器、登录延迟预算 100–500 ms 定的。上线前最好还是在自己的硬件上跑基准。

Argon2id 参数:首选

OWASP 基线推荐:m=19456 (19 MiB), t=2, p=1

服务器内存余量更充足的话,可以在内存与时间之间转移工作量。RFC 9106 给出了等效配置档,OWASP 接受其中任一组合:

memoryCost (m)timeCost (t)parallelism (p)每次哈希 RAM
471041146 MiB
194562119 MiB(基线)
122883112 MiB
9216419 MiB
7168517 MiB

调参经验。先按峰值并发登录的 RAM 预算确定 m。比如同时 100 路登录、4 GiB 余量,那就是每次哈希 40 MiB。然后把 t 调高,直到生产 CPU 上单次 verify 落在 100–500 ms。除非确实有多核理由,否则 p=1 别动(多数 Web 框架已经为每个请求分配了独立线程)。

scrypt 参数:Argon2 不可用时

OWASP 推荐:N=2^17 (131072), r=8, p=1,每次哈希 128 MiB。

如果每路并发 128 MiB 对你的服务器太重,OWASP 也允许更弱的配置:

Nrp每次哈希 RAM
2^1781128 MiB(优选)
2^168164 MiB
2^158132 MiB

N 必须是 2 的幂。r 增大会按比例同时增加内存和 CPU 工作量;p 增大只加 CPU 工作量,不加单实例内存。密码哈希场景里,rp 留默认,只调 N

bcrypt:仅限遗留系统,cost ≥ 10

OWASP 已经不再向新项目推荐 bcrypt,但它在 Devise、Spring Security、ASP.NET Identity,还有一堆自研认证系统里仍然是默认选择。

非用 bcrypt 不可时,规则是:

  • cost factor 最低 10。低于 10,单张 GPU 几天内就能跑完一个泄露库。
  • 推荐 12 到 14,看硬件。2024 年的现代 x86 服务器上,cost=12 每次哈希约 250 ms,cost=13 约 500 ms。
  • 生产硬件上目标 每次 verify 100–300 ms。要测,别猜。
  • 记住 72 字节输入限制。允许 passphrase 的话,先用 SHA-256 预哈希(详见 FAQ)。

bcrypt 抗 GPU 能力被 4 KiB 内存占用卡住,cost 调多高都不可能赶上 Argon2id 的内存困难性。能上 Argon2id 就上 Argon2id。

一个参考:2024 年的 EPYC 服务器上,bcrypt(cost=12) 约 250 ms;高端笔记本接近 350 ms。如果实测和 100–500 ms 差出一个数量级,先看看你的库跑的到底是原生 bcrypt,还是退化到了某个慢吞吞的 JavaScript polyfill(一些 bundler 在 serverless 构建里会把原生依赖剥掉)。

PBKDF2:留给 FIPS-140 合规

PBKDF2(RFC 8018)在安全建议里是最后的兜底。它比 bcrypt 还老,不是内存困难的,GPU 攻击面前比上面三个倒得都快。但它是唯一通过 FIPS-140 验证的密码哈希原语,对联邦政府、HIPAA 医疗、部分金融部署来说这就是硬条件。

要用 PBKDF2 的话:

  • PRF 用 HMAC-SHA-256(不要用 SHA-1,也不要用没有 HMAC 包装的纯 SHA-256)
  • 至少 600,000 次迭代(OWASP 2026 基线)
  • 每个密码至少 16 字节随机 salt

FIPS 不适用就选 Argon2id。PBKDF2 固定输出、固定内存的设计,意味着攻击者每多花一块钱在 GPU 上,就直接换来更多的密码尝试速率。

NIST 的 SP 800-63B 把 PBKDF2-HMAC 列为「approved」用于密码哈希,但没说它比内存困难替代更好。可以这么理解:NIST 允许 PBKDF2,是因为淘汰它会让所有遗留政府部署当场失效,不是因为它对绿地项目最优。

选型:到底用哪个

对比表

维度bcryptscryptArgon2idPBKDF2
内存困难
抗 GPU极高
抗侧信道高(id)
参数复杂度1(cost)3(N、r、p)3(m、t、p)1(iterations)
库生态成熟度极好极好
输入长度限制72 字节
标准化事实标准RFC 7914RFC 9106RFC 8018
OWASP 2026 状态仅遗留备选首选仅 FIPS

默认用 Argon2id

新项目(常见 Web 应用,现代 Node/Python/Go/Rust/JVM 技术栈,没有 FIPS 约束)就用 Argon2id,参数 m=19456, t=2, p=1。能拿到目前最强的抗 GPU 和抗侧信道能力,参数嵌在哈希里(对库升级友好),没有输入长度陷阱。库生态也成熟:npm 上的 argon2、PyPI 上的 argon2-cffigolang.org/x/crypto/argon2、crates.io 上的 argon2 crate,都有人在维护和跑基准。

何时改用 scrypt 或 bcrypt

选 scrypt 的情况:你的运行时上不了 Argon2(2026 年确实少见,连 Cloudflare Workers 和 Deno 现在都能跑);或者生产已经基于 scrypt 部署,迁移成本高于安全收益。scrypt 仍然是个稳健的算法,就是少了 Argon2id 的抗侧信道精修。

选 bcrypt 的情况:在维护遗留系统,依赖最小化要求很硬(不能用原生代码、不能加包),用户群也能接受 72 字节输入限制。bcrypt 在互联网规模部署了二十年,失效模式研究得很透。

选 PBKDF2 的情况:监管要求如此。就这一个理由。审计员认可 Argon2id 的话(非 FIPS 工作负载下越来越多),那就上 Argon2id。

常见错误

过去十年的密码存储事故,绝大多数都能追溯到一小撮反复出现的工程错误。这些坑都不算冷门,把下面这张清单贴屏幕上,对着自己的认证代码逐项过一遍,基本都能挖出来。

  • 直接用 SHA-256 或 MD5 哈希密码。这是密码存储事故的头号原因。原因见 MD5 与 SHA-256 对比
  • 所有用户共用一个全局 salt。salt 必须每条记录唯一。Argon2 和 bcrypt 会自动生成,别去覆盖。
  • 把哈希时间设到 50 ms 以下。这是拿安全性换一个用户根本感觉不到的速度提升。目标 100–500 ms。
  • 把哈希时间设到 1 秒以上。这给登录端点制造了一个拒绝服务向量。封顶约 500 ms。
  • 在客户端哈希密码再把摘要发到服务端。摘要现在就是新密码。任何拿到数据库的人都能直接登录,连逆推都省了。哈希在服务端做。
  • 把算法参数另存一列。PHC 字符串格式已经把它们写进哈希了,直接用。
  • 错误处理时打印密码或哈希。这两样都属于用户,不属于你的日志聚合系统。在请求解析层就脱敏,别让它们流进任何 logger。
  • verify() 抛的异常当作认证失败。库对损坏的存储哈希抛异常,应该把这个错误暴露出来,而不是悄悄走到「密码错误」分支。把「密码错误」(返回 401)和「存储哈希损坏」(返回 500 并叫醒值班)分开。

落地实现

Node.js 用 Argon2id

argon2 包(参考实现的原生绑定)是 Node 上的标准选择:

import argon2 from 'argon2';

// 注册或修改密码时进行哈希
const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456,  // 19 MiB
  timeCost: 2,
  parallelism: 1,
});
// → '$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>'

// 登录时验证
const ok = await argon2.verify(hash, candidate);
if (!ok) throw new Error('Invalid credentials');

// 检测过期参数,登录成功后再哈希
if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) {
  const upgraded = await argon2.hash(candidate, {
    type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
  });
  await db.users.update({ id: user.id }, { password_hash: upgraded });
}

needsRehash 这一步是长期迁移的关键:每次成功登录都顺便把存储哈希升级到当前参数,用户全程没感知。

Python 同样的写法,用 argon2-cffi

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)

# 哈希
stored = ph.hash(password)

# 验证
try:
    ph.verify(stored, candidate)
except VerifyMismatchError:
    raise ValueError('Invalid credentials')

# 参数升级时再哈希
if ph.check_needs_rehash(stored):
    stored = ph.hash(candidate)

Go 用 golang.org/x/crypto/argon2

import (
    "crypto/rand"
    "golang.org/x/crypto/argon2"
)

func hashPassword(password string) ([]byte, []byte) {
    salt := make([]byte, 16)
    rand.Read(salt)
    hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
    return hash, salt
}

Go 标准库不带 PHC 格式编码器;直接用 argon2.IDKey 原语时,参数和 salt 怎么和哈希一起编码得自己处理。多数 Go 项目用 github.com/alexedwards/argon2id 这类封装来兜底。

Rust 用 argon2 crate,同样直白:

use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}};

let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();  // 默认即 Argon2id, m=19456, t=2, p=1
let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();

// 验证
let parsed = argon2::password_hash::PasswordHash::new(&hash)?;
argon2.verify_password(candidate.as_bytes(), &parsed)?;

这三种运行时之间生成的字符串可互换:Node 创建的哈希,Python 或 Rust 都能干净通过验证。这种跨运行时兼容性,在多语言架构里让 Argon2 比那些算法专属封装好用得多。

从 bcrypt 迁到 Argon2id 的做法

基本不会有清空用户表从头来一遍的机会。正确的迁移做法和 hash 生成器 FAQ 里的 MD5 转 bcrypt 段落一致:登录驱动的软迁移。

加一列记录算法:

ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt';

登录时按算法分发到对应验证器:

async function verifyAndMaybeRehash(user, candidate) {
  let ok;
  if (user.password_algo === 'argon2id') {
    ok = await argon2.verify(user.password_hash, candidate);
  } else if (user.password_algo === 'bcrypt') {
    ok = await bcrypt.compare(candidate, user.password_hash);
    if (ok) {
      // 旧算法验证通过 → 立即用 Argon2id 重新哈希
      const newHash = await argon2.hash(candidate, {
        type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
      });
      await db.users.update({ id: user.id }, {
        password_hash: newHash,
        password_algo: 'argon2id',
      });
    }
  }
  return ok;
}

设一个 6–12 个月的日落窗口。第 9 个月发一封「您的密码用过时方式存储,登录一次即可升级」邮件。12 个月后,仍在 bcrypt 的账户下次登录时强制重置密码。活跃用户透明迁移,不活跃账户经历一次性的摩擦。

从 scrypt 或 PBKDF2 迁出也是同一套做法,需要的状态只有 password_algo 一列。

Pepper、长度限制和编码陷阱

几个真实部署里常踩的边角:

Pepper。pepper 是应用级密钥,哈希前和每个密码混合,跟数据库分开放(KMS、env var、Hashicorp Vault 之类)。数据库泄露但应用密钥没泄露的话,泄露的哈希拿不到 pepper 就攻不动。施加方式用 HMAC,不要拼接:

import { createHmac } from 'crypto';
const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest();
const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ });

Pepper 别频繁轮转(轮转得重新哈希),但要支持版本化轮转:PEPPER_V2,验证时回退到 PEPPER_V1

bcrypt 72 字节限制。非用 bcrypt 又想支持任意长度密码,先用 SHA-256 预哈希再 base64 编码(避开嵌入的 NUL 字节,bcrypt 对 NUL 的处理也不一致):

import { createHash } from 'crypto';
const prepped = createHash('sha256').update(password, 'utf8').digest('base64');
const hash = await bcrypt.hash(prepped, 12);

验证时也得执行同样的 prepped 变换。在认证代码里加一段醒目注释,半年后回来看时会很庆幸。

UTF-8 归一化。字符串 "café" 可以编成 c-a-f-é(4 个码点,NFC),也可以编成 c-a-f-e + 组合用尖音符(5 个码点,NFD)。看起来一模一样,哈希结果不同。哈希前统一归一化到 NFC:

const normalized = password.normalize('NFC');

移动键盘和从 PDF 复制粘贴触发这个坑的频次,比想象中高得多。

别在客户端预哈希。客户端算出来发到服务端的哈希,就是新密码。任何能读到数据库的人都能直接过认证。哈希在服务端做,没有例外。JWT 也改变不了这点;JWT 能认证什么、不能认证什么,见如何解码 JWT Token

在生产硬件上跑基准,不要拿笔记本测。一台 13 代 Intel 笔记本跑 Argon2id m=19456, t=2, p=1 约 35 ms。同样参数在 t3.small EC2 实例上接近 180 ms,在 Raspberry Pi 4 上超过 600 ms。挑出真正会跑生产的硬件,跑 1,000 次 verify,按中位数调参。serverless 容器冷启动带来的登录延迟波动也值得测一下——Lambda 冷启动可能跟哈希无关地多出 200–800 ms。

FAQ

密码哈希和加密有什么区别?

哈希是单向的:算出一个定长指纹,无法逆推回输入。加密是双向的:有正确的密钥就能解密回原文。密码要哈希,不要加密。服务端不应该有能力恢复任何用户的密码——这样数据库泄露才不会变成凭据泄露。

为什么不能直接用 SHA-256 哈希密码?

SHA-256 是为速度设计的。一张现代 GPU 每秒能算 220 亿次 SHA-256,泄露库里一个 8 字符小写密码几分钟就会失守。密码哈希需要 SHA-256 没有的三件事:刻意慢的执行、每条记录独立 salt、内存困难。这套权衡和 hash 生成器里「不要用 MD5 做安全用途」的提示一致;攻击者如何把弱哈希还原成明文,可参阅密码熵详解

bcrypt 2026 年还安全吗?

bcrypt 本身没被攻破,基于 Blowfish 的密钥调度在密码学上仍然成立。变的是威胁模型:GPU 和 ASIC 让 bcrypt 没有内存困难这件事,相对 Argon2id 成了明显弱点。OWASP 2026 的态度是:cost ≥ 10 的 bcrypt 在遗留系统里可以接受,新项目用 Argon2id。

Argon2i、Argon2d、Argon2id 用哪个?

Argon2id。RFC 9106 指定它为密码哈希的推荐变体。Argon2i 是数据无关的(抗侧信道但对 GPU 时空折中弱),Argon2d 是数据相关的(抗 GPU 但易受缓存时序侧信道攻击),Argon2id 是混合体,一份成本拿到两个属性。

我的应用要怎么选 Argon2id 参数?

从 OWASP 基线起步:m=19456, t=2, p=1。然后在生产 CPU 上做基准并调整:

  1. 定下每路登录的 RAM 预算(比如峰值并发下 50 MiB)。
  2. m 设到该值或更低。
  3. 在循环里跑 argon2.hash(),测墙钟时间。
  4. t 往上调,直到中位耗时落在 100 到 500 ms 之间。

除非已经 profile 过,确认多 lane 并行对你的运行时有帮助,否则 p=1 别动。高流量认证服务器一般偏向更高的 t 加更低的 m,能腾出更多 RAM 余量。

bcrypt 的 72 字节限制是什么?长 passphrase 怎么办?

bcrypt 把输入喂给 Blowfish 密钥调度,后者在 72 字节处截断。150 字符的 passphrase 安全度只等于前 72 字节,后面忽略掉。处理办法:先用 SHA-256(32 字节)或 SHA-512(64 字节)预哈希,摘要 base64 编码以避开 NUL,再喂给 bcrypt。Argon2id 和 scrypt 没这个限制,直接接受任意长度输入。

能不能在不强制重置密码的情况下从 bcrypt 迁到 Argon2?

可以。做法:password_algo 列后面同时存两种算法,验证时分发到对应库;每次 bcrypt 验证成功后,立刻用 Argon2id 重新哈希并更新该行。活跃用户在正常登录节奏里静默迁移完成。给不活跃账户设一个 6–12 个月的日落窗口,到期后还在 bcrypt 的记录强制重置密码。算法到算法的迁移都是这一套。

PBKDF2 在 2026 年还是好选择吗?

只有 FIPS-140 合规逼你这么做时是。典型场景是联邦政府、受监管的医疗(HIPAA)、部分金融系统。PRF 用 HMAC-SHA-256,迭代至少 600,000 次。PBKDF2 不是内存困难的,同等延迟预算下比 Argon2id 更快地倒在 GPU 攻击面前。FIPS 不适用就选 Argon2id,省掉这套合规折腾。


2026 年的密码哈希答案很短:默认 Argon2id 配 OWASP 基线参数;Argon2 用不了就退回 scrypt;bcrypt 只在遗留系统逼你时保留;PBKDF2 留给 FIPS 约束系统。再配上每条记录独立的 salt(现代库都自动处理)、存在数据库之外的应用级 pepper,以及一个登录驱动的再哈希循环,硬件升级时就能顺着把工作因子一路调上去。

随机密码生成器生成一组有代表性的密码集,在生产 CPU 上跑 verify 路径的基准,把参数写进一个常量文件,这样 2028 年下一位工程师要拧哪颗螺丝一目了然。完整的安全上下文(TLS、会话管理、限速、MFA)都在 Web 安全最佳实践指南里。给应用选对哈希。