在配置项里看到 Argon2 有三种类型可选,很多人会按「哪个最难破解就选哪个」的思路去挑。如果按这个标准,argon2d 的抗 GPU 能力最强,似乎应该选它。但官方文档和 OWASP 的推荐却是 argon2id。要理解这个选择,需要先弄清三种变体在内存访问方式上的差异。
三种变体的根本差别在内存访问
Argon2 的核心是填充一大块内存,然后反复在这些内存块之间做运算。三种变体的差别在于:下一个要访问的内存块地址是怎么算出来的。
argon2d 的地址是数据相关的——下一个访问位置取决于当前块里的数据内容。这样设计的好处是攻击者无法预先知道访问顺序,很难优化,抗 GPU 和抗 ASIC 能力最强。
argon2i 的地址是数据无关的——访问顺序只由输入参数决定,与内存里的数据无关。这样设计的好处是访问模式可预测,攻击者无法通过观察内存访问的时间或缓存命中情况来推断数据内容。
argon2id 是混合方案:第一遍用数据无关的方式访问,后续几遍用数据相关的方式。
| 类型 | 内存访问 | 抗侧信道 | 抗 GPU | 适用场景 |
|---|---|---|---|---|
| argon2d | 数据相关 | 弱 | 最强 | 无侧信道威胁的封闭环境 |
| argon2i | 数据无关 | 强 | 稍弱 | 存在侧信道威胁的环境 |
| argon2id | 混合 | 较强 | 强 | 通用默认值 |
什么是侧信道威胁
要理解为什么「最强」的 argon2d 反而不被推荐,需要先理解侧信道攻击。
侧信道指的是通过观察计算过程的外部特征来推断内部数据,而不是直接分析算法的数学性质。常见的侧信道包括执行时间、内存访问模式、缓存命中率、功耗和电磁辐射。
对密码哈希来说,如果一个攻击者能在同一台物理机上观察你的计算过程(比如云环境的共享主机、或者能运行测量程序的场景),他可能通过记录哪些内存地址被访问,反推出部分内部状态,从而降低破解难度。
数据相关的访问顺序会让这种观察变得有信息量——访问了哪块内存,间接泄露了数据内容。而数据无关的访问顺序则不会泄露这些信息,因为访问模式与数据无关。
这就是 argon2i 存在的意义:在可能被观测的环境里牺牲一点抗 GPU 能力,换取抗侧信道能力。
为什么 argon2id 成为默认
argon2id 的混合设计试图兼顾两者。
第一遍用数据无关的访问,这一步可以抵抗侧信道观察;后续的迭代用数据相关访问,这一步保留了抗 GPU 的能力。因为攻击者需要完整地观测所有轮次才能获得有效信息,而第一轮的不可预测性已经降低了泄露的价值。
RFC 9106 和 OWASP 都推荐把 argon2id 作为默认选择。这不是折中的妥协,而是经过分析后的结论:在绝大多数部署环境里,argon2id 提供了最合适的平衡。
什么情况下才考虑其他两个
argon2i 适合确实存在侧信道威胁的场景。比如某些嵌入式环境、共享内存的沙箱、或者安全等级要求把所有可观测通道都堵住的场合。代价是抗 GPU 能力下降,需要通过提高 m 来补偿。
argon2d 的适用面很窄。它只在能完全排除侧信道威胁的封闭环境里才有意义,比如单租户的专用硬件、物理隔离的服务器。在这种环境下,选择 argon2d 可以获得最高的抗 ASIC 能力。
而对于绝大多数 Web 应用、API 服务、企业内部系统,侧信道威胁的实际风险远低于暴力破解,所以不需要为此牺牲抗 GPU 能力。
校验时的兼容性
一个容易被忽略的点是:三种变体互不兼容。同一个密码、同样的参数,用 argon2i 和 argon2id 算出的结果完全不同。
所以哈希串里必须写清楚用的是哪一种变体——编码串的开头 $argon2id$ 就是做这个的。校验函数读取这个标识后选择对应的算法,不需要人工干预。
这也意味着不能随意更换变体。如果要把存量数据的 argon2i 换成 argon2id,必须保留旧哈希按原变体校验,在用户登录成功时用新变体重新计算并覆盖。
检查清单
- 三种变体的差别在内存访问方式:数据相关、数据无关、混合
- argon2d 抗 GPU 最强但存在侧信道风险,服务端不建议使用
- argon2i 抗侧信道强,适合存在观测威胁的环境
- argon2id 是 RFC 9106 与 OWASP 共同推荐的默认值
- 三种变体互不兼容,哈希串开头的标识决定校验方式
- 更换变体时必须保留旧哈希按原方式校验,在登录时顺带升级