密码安全与 Argon2 原理
认证系统首先要安全保存密码。JWT 只能在登录成功后表示身份,不能代替密码哈希。
1. 为什么不能保存明文密码
错误的数据结构:
{
"username": "zhangsan",
"password": "123456"
}
一旦数据库、备份或日志泄露,攻击者会立即得到所有密码。用户还可能在多个网站复用密码,影响范围会扩散到其他系统。
数据库应该保存:
{
"username": "zhangsan",
"passwordHash": "$argon2id$v=19$m=65536,t=3,p=4$..."
}
2. 为什么不能只用 SHA-256
下面的代码看似没有保存明文,但仍不适合密码:
createHash("sha256").update(password).digest("hex");
SHA-256 很快,攻击者可以用 GPU 或专用硬件每秒尝试海量候选密码。密码哈希需要故意变慢,并增加内存成本。
3. Argon2 的三种类型
- Argon2d:数据依赖型内存访问,侧重抵抗 GPU 破解,但更容易受到侧信道影响。
- Argon2i:数据无关型内存访问,侧重抵抗侧信道攻击。
- Argon2id:混合两者的策略,通常推荐用于密码哈希。
node-argon2 官方 README 明确说明默认类型是 Argon2id,因此下面两种写法在当前版本中目标一致:
await argon2.hash(password);
await argon2.hash(password, {
type: argon2.argon2id,
});
教程显式写 argon2.argon2id,便于学习者看懂选择。
4. 成本参数
Argon2 常见参数:
memoryCost:每次哈希使用的内存量,单位是 KiB。timeCost:计算迭代成本。parallelism:并行度。hashLength:输出哈希长度。
成本越高,不代表无条件越安全。它也会占用服务端资源,若配置过高,攻击者甚至可以通过大量登录请求造成拒绝服务。
官方 README 建议密码哈希通常直接采用库提供的安全默认参数,不需要初学者随意修改。生产调优时应在实际服务器压测,并结合登录限流。
5. PHC 字符串
一个编码后的 Argon2id 哈希类似:
$argon2id$v=19$m=65536,t=3,p=4$c2FsdA$SGFzaA
可分为:
$算法$v=版本$参数$盐$哈希结果
这意味着验证时只需提供完整哈希和用户输入的密码:
await argon2.verify(storedHash, inputPassword);
库能从哈希字符串读取原来的盐和成本参数。
6. 为什么相同密码得到不同哈希
const first = await argon2.hash("same-password");
const second = await argon2.hash("same-password");
console.log(first === second); // false
每次使用了不同的随机盐。不能在登录时重新 hash() 后通过字符串相等判断密码:
// 错误:新盐会产生不同字符串
const inputHash = await argon2.hash(inputPassword);
const matched = inputHash === user.passwordHash;
正确方式:
const matched = await argon2.verify(
user.passwordHash,
inputPassword
);
7. 参数升级
硬件会变快,安全建议也会更新。若数据库中的旧哈希成本低于当前策略,可以在用户登录成功后升级:
verify 成功
-> needsRehash 判断旧参数
-> 用本次已经验证正确的明文密码重新 hash
-> 更新 passwordHash
这叫渐进式迁移:不要求所有用户同时重置密码,活跃用户会在登录时逐步升级。
8. 业务层仍需做什么
Argon2 不能独自解决全部密码安全问题。还需要:
- 使用 HTTPS。
- 设置合理的最低密码长度。
- 对登录和注册接口限流。
- 不在日志记录密码。
- 登录失败统一提示“账号或密码错误”。
- 对重置密码流程做额外身份验证。
- 密码修改后按业务风险撤销已有会话。
- 防止请求体过大导致资源浪费。
9. Argon2 与 JWT 的对比
| 问题 | Argon2 | JWT 签名 |
|---|---|---|
| 保护对象 | 用户密码 | Token 内容完整性和来源 |
| 是否可逆 | 不可逆 | Payload 可读取,不存在“解密” |
| 是否使用随机盐 | 是 | 不适用 |
| 典型调用 | hash、verify |
sign、verify |
| 使用阶段 | 注册、登录、改密 | 登录成功后和后续请求 |