MD5 太快了,快到黑客可以每秒尝试 1000 亿次。Argon2 反其道而行之——故意让自己变慢。

一个反直觉的想法

在大多数场景下,我们追求的是"快":

  • 网页加载要快
  • 数据库查询要快
  • 文件传输要快

但密码哈希是个例外。这里我们需要"慢"。

为什么慢反而是好事?

想象两种锁:

快锁(MD5)

尝试一把钥匙只需 0.00000001 秒小偷每秒能试 1000 亿把钥匙 6 位密码锁:0.00001 秒破解

慢锁(Argon2)

尝试一把钥匙需要 0.5 秒小偷每秒只能试 2 把钥匙 6 位密码锁:6 天破解

对于正常用户

  • 登录时等 0.5 秒?完全可以接受
  • 反正只输一次密码

对于黑客

  • 每次尝试都要 0.5 秒?灾难!
  • 原本 1 秒破解的密码,现在要几个月

这就是"慢哈希"的精髓:让暴力破解变得不经济。

Argon2:专为密码设计

Argon2 是 2015 年"密码哈希竞赛"(Password Hashing Competition)的冠军。这场比赛的目的就是找出最适合存储密码的算法。

为什么不用 bcrypt?

在 Argon2 之前,bcrypt(1999 年)是最流行的密码哈希算法。它已经够慢了,为什么还需要 Argon2?

答案是:GPU 和专用硬件(ASIC)

bcrypt 的问题

bcrypt 设计于 1999 年,那时候:

  • 个人电脑只有 CPU
  • 没有 GPU 并行计算
  • 没有专用破解硬件 (ASIC)

今天的问题:

  • bcrypt 虽然需要 4KB 内存,但相比 Argon2 仍然很少
  • GPU 可以通过并行运行多个 bcrypt 实例来加速(虽然 bcrypt 有一定抗 GPU 设计,加速比远不如 MD5 那么夸张,但仍有优势)
  • ASIC 可以针对 bcrypt 进行专门优化
  • 结果:攻击者用专用硬件可以获得一定优势

⚠️ bcrypt 的"慢"优势被硬件发展抵消了

Argon2 的秘密武器:吃内存

Argon2 的核心创新是:不仅消耗时间,还消耗大量内存

为什么内存是关键?

硬件特点内存情况
CPU2-4 核大内存 32GB+
GPU数千小核心共享有限内存 (数 GB 共享)
ASIC极多核心内存极贵 (成本爆炸)

不同算法的硬件优势对比

算法CPUGPUASIC
MD5 (极快)1x10000x+ ⚠️100000x+ ⚠️
bcrypt (慢+4KB内存)1x5-50x ⚠️数十倍 ⚠️
Argon2 (慢+大内存)1x2-5x ✓2-5x ✓

注:Argon2 的 GPU/ASIC 优势取决于内存配置。使用推荐的 64MB+ 内存时,GPU 难以大规模并行;但低内存配置下仍有加速空间。数据为估算值,具体取决于硬件和参数配置。

GPU 每个核心都需要独立的大内存,成本剧增。ASIC 上放大内存比放逻辑电路贵得多。Argon2 大幅缩小了硬件差距。

Argon2 通过"吃内存"让 GPU 和 ASIC 的优势大幅降低,使攻击成本显著提高。

Argon2 的三种口味

Argon2 有三个变体,针对不同场景:

变体特点适用场景
Argon2d抗 GPU/ASIC,但可能受侧信道攻击加密货币挖矿等
Argon2i抗侧信道攻击,但抗 GPU 稍弱前端/加密密钥派生
Argon2id前两者的混合,平衡安全性通用推荐(OWASP 推荐)

简单原则:不确定用哪个?选 Argon2id。

Argon2 的三个旋钮

Argon2 让你自己调节"有多慢":

三个关键参数

参数含义推荐值
time_cost迭代次数,越大越慢3-5
memory_cost使用的内存大小 (KB)65536 (64MB) 或更高
parallelism使用的线程数CPU 核心数

配置示例

$argon2id$v=19$m=65536,t=3,p=4$salt$hash

参数含义
m6553664MB 内存
t33 次迭代
p44 线程

实际代码长什么样?

Python 示例

from argon2 import PasswordHasher

# 创建哈希器(使用默认的安全配置)
ph = PasswordHasher(
    time_cost=3,        # 迭代次数
    memory_cost=65536,  # 64MB 内存
    parallelism=4       # 4 线程
)

# 注册时:哈希密码
password = "用户输入的密码"
hash = ph.hash(password)
# 存储 hash 到数据库

# 登录时:验证密码
try:
    ph.verify(hash, password)
    print("密码正确!")
except:
    print("密码错误!")

Rust 示例

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

// 注册时:哈希密码
let password = b"user_password";
let salt = SaltString::generate(&mut rand::thread_rng());
let argon2 = Argon2::default();
let hash = argon2.hash_password(password, &salt)?.to_string();
// 存储 hash 到数据库

// 登录时:验证密码
let parsed_hash = PasswordHash::new(&hash)?;
argon2.verify_password(password, &parsed_hash)?;

一张图看懂 Argon2 的内部

Argon2 内存矩阵工作原理

  1. 输入:密码 + 盐 + 参数
  2. Round 1 - Fill (填充阶段):生成内存块 [B0] [B1] [B2] ... [Bn]
  3. Round 2 - Mix (混合阶段):块之间交叉依赖,生成 [B0'] [B1'] [B2'] ... [Bn']
  4. 重复 N 轮混合操作
  5. 输出:最终哈希值

关键点:每个块都依赖其他块,你不能只算一小部分。必须把整个内存矩阵都填满、都保留。这就是为什么它"吃内存"。

Argon2 vs 其他算法

算法发明年份内存需求抗 GPU抗 ASIC推荐程度
MD51991极低⛔ 禁止
SHA-2562001极低⛔ 禁止
bcrypt19994KB⚠️ 一般⚠️ 一般✅ 可用
scrypt2009可配置✅ 好✅ 好✅ 推荐
Argon22015可配置极好极好首选

常见问题

Q:Argon2 会不会让我的服务器变慢?

A:会,但这是值得的。

场景分析

登录请求密码验证开销影响
100/分钟0.1 秒/次10 秒/分钟 CPU,可接受 ✓
1000/分钟0.1 秒/次100 秒/分钟 CPU,需要考虑限流 ⚠️
10000/分钟0.1 秒/次可能需要专用验证服务 ⚠️

优化建议

  • 登录失败时增加延迟(防暴力破解)
  • 使用验证码减少无效请求
  • 大规模系统可考虑专用认证服务

Q:应该用多大的内存?

A:在你的服务器能承受的范围内,尽量大。

内存配置决策

服务器内存推荐 memory_cost
< 4GB32768 (32MB)
4-16GB65536 (64MB)
> 16GB131072+ (128MB+)

然后测试

  • 单次哈希应在 0.1-0.5 秒
  • 太快?增加 time_costmemory_cost
  • 太慢?减少参数(但别低于推荐值)

推荐最低配置

  • memory_cost = 65536 (64MB)
  • time_cost = 3
  • parallelism = 4

Q:旧密码用 MD5 存的,怎么迁移?

A:渐进式迁移。

步骤 1:双哈希过渡

旧用户登录时:

  1. 输入密码 → MD5 → 比对成功?
  2. 用原始密码重新计算 Argon2
  3. 更新数据库,标记为"已迁移"

步骤 2:数据库结构

usernamehash_typepassword_hashmigrated
aliceargon2id$argon2id$…true
bobmd5e10adc…false

步骤 3:验证逻辑

if user.migrated:
    verify_argon2(password, hash)
else:
    if verify_md5(password, hash):
        new_hash = argon2_hash(password)
        update_user(new_hash, migrated=True)

用户无感知,渐进完成迁移。

Argon2 的局限性

虽然 Argon2 是目前最好的密码哈希算法,但它仍有局限:

Argon2 的局限性

  • 不能防止弱密码123456 用 Argon2 哈希后,暴力破解仍然只是时间问题(只是从毫秒变成小时/天)
  • 密码仍然要发送到服务器:服务器在验证时会"看到"明文密码(即使只是短暂的一瞬间),如果服务器被攻破,密码仍可能泄露
  • 数据库泄露后仍可离线破解:攻击者拿到哈希值后,可以慢慢暴力破解,只是需要更长时间(对于弱密码可能仍然可行)
  • 需要服务器资源:每次验证都需要 CPU 和内存,可能成为 DDoS 攻击的放大点

解决方案:结合强密码策略 + 多因素认证。更进一步:SRP / OPAQUE(本系列后续文章)。

总结

密码哈希进化

算法年份速度内存GPU 抵抗推荐
MD51991极快秒破
bcrypt1999少 (4KB)可破
Argon22015大 (可配置)无效✓✓

Argon2:2015 密码哈希竞赛冠军 | RFC 9106 | 行业标准

Argon2 通过两个维度增加破解成本:

  1. 时间成本:每次计算需要更长时间
  2. 内存成本:每次计算需要大量内存,让 GPU/ASIC 失去优势

如果你正在开发一个需要存储密码的系统,请使用 Argon2id。这是当前的行业最佳实践。


但是,即使用了 Argon2,密码仍然要传到服务器上,服务器仍然会"见到"你的密码(即使只是短暂地)。有没有办法让服务器永远不知道你的密码呢?

这就是我们下一篇的主题:SRP 协议——一种让服务器永远不知道你密码的认证方案。


上一篇:MD5:一部血泪史 下一篇:SRP:证明你知道密码,却不说出密码

本系列:

  1. MD5:一部血泪史
  2. Argon2:慢哈希的艺术(本篇)
  3. SRP:证明你知道密码却不说出密码
  4. OPAQUE:防离线破解的终极方案