node-argon2 教程
node-argon2 是 Argon2 参考实现的 Node.js 绑定,提供适合密码哈希的 API 和 PHC 字符串格式。
1. 安装与导入
npm install argon2
它自带 TypeScript 类型声明,不需要安装 @types/argon2。
结合当前 CommonJS 编译配置,使用官方 TypeScript 示例风格:
import * as argon2 from "argon2";
argon2 包含原生二进制组件。当前官方 README 表示最新版本只针对 Node.js 22 及以上进行测试,并为常见平台提供预编译二进制。安装失败时应先检查 Node.js 版本、操作系统支持和官方安装说明,而不是随意降低安全参数。
2. argon2.hash():哈希密码
const passwordHash = await argon2.hash("correct horse battery staple", {
type: argon2.argon2id,
});
推荐封装:
async function hashPassword(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
});
}
注册时只保存 passwordHash:
const user = {
id: "user-123",
username: "zhangsan",
passwordHash: await hashPassword(inputPassword),
};
不要自己使用固定盐:
// 不推荐:破坏默认的安全随机盐策略
await argon2.hash(password, {
salt: Buffer.from("fixed-salt"),
});
默认情况下库会生成随机盐并将其包含在结果中。
3. 是否需要修改默认参数
官方 README 的建议是:用于密码哈希时,通常无需修改默认安全参数。
学习时优先使用:
const passwordHash = await argon2.hash(password, {
type: argon2.argon2id,
});
只有在你已经完成服务器压测、明确安全目标和资源预算后,再统一定义参数:
const passwordHashOptions: argon2.Options & { raw?: false } = {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 4,
};
这里的数字只是展示参数含义,不应脱离当前库版本、服务器硬件和安全标准盲目复制。
4. argon2.verify():验证密码
参数顺序是:
argon2.verify(数据库中的哈希, 用户输入的密码)
完整示例:
async function verifyPassword(
passwordHash: string,
inputPassword: string
): Promise<boolean> {
return argon2.verify(passwordHash, inputPassword);
}
- 返回
true:密码匹配。 - 返回
false:密码不匹配。 - 抛出异常:哈希格式损坏、原生模块失败等内部问题。
业务代码应区分“不匹配”和“系统异常”:
try {
const matched = await argon2.verify(
user.passwordHash,
inputPassword
);
if (!matched) {
// 正常的登录失败
}
} catch (error) {
// 内部错误:记录安全日志并返回通用服务错误
}
5. argon2.needsRehash():检查是否应升级
needsRehash() 比较哈希中记录的参数与当前要求:
if (argon2.needsRehash(user.passwordHash)) {
user.passwordHash = await argon2.hash(inputPassword, {
type: argon2.argon2id,
});
}
应当只在密码验证成功后执行:
async function verifyAndUpgradePassword(
user: { passwordHash: string },
inputPassword: string
): Promise<boolean> {
const matched = await argon2.verify(
user.passwordHash,
inputPassword
);
if (!matched) {
return false;
}
if (argon2.needsRehash(user.passwordHash)) {
user.passwordHash = await argon2.hash(inputPassword, {
type: argon2.argon2id,
});
}
return true;
}
如果团队使用显式参数策略,检查和重新哈希必须传入同一策略:
if (argon2.needsRehash(user.passwordHash, passwordHashOptions)) {
user.passwordHash = await argon2.hash(
inputPassword,
passwordHashOptions
);
}
6. argon2.argon2id
argon2.argon2id 是库导出的类型常量:
const passwordHash = await argon2.hash(password, {
type: argon2.argon2id,
});
当前库默认也是 Argon2id。显式写出有三个学习价值:
- 让读者知道正在使用哪种 Argon2 变体。
- 让团队的安全策略在代码中可见。
- 如果未来库默认值变化,代码意图仍然明确。
7. 一个注册与登录核心函数
import * as argon2 from "argon2";
interface User {
id: string;
username: string;
passwordHash: string;
}
async function createUser(
username: string,
password: string
): Promise<User> {
const passwordHash = await argon2.hash(password, {
type: argon2.argon2id,
});
return {
id: crypto.randomUUID(),
username,
passwordHash,
};
}
async function checkLogin(
user: User,
inputPassword: string
): Promise<boolean> {
const matched = await argon2.verify(
user.passwordHash,
inputPassword
);
if (!matched) {
return false;
}
if (argon2.needsRehash(user.passwordHash)) {
user.passwordHash = await argon2.hash(inputPassword, {
type: argon2.argon2id,
});
}
return true;
}
注意:示例中的 crypto.randomUUID() 假定你导入了 randomUUID 或使用了可用的全局 Web Crypto。为了与 Node.js 代码风格保持一致,实际案例会显式写 import { randomUUID } from "node:crypto"。
8. 常见错误
// 错误:保存明文
user.password = inputPassword;
// 错误:重新 hash 后比较字符串
await argon2.hash(inputPassword) === user.passwordHash;
// 错误:把参数顺序颠倒
await argon2.verify(inputPassword, user.passwordHash);
// 错误:把 passwordHash 返回给客户端
res.json(user);
正确做法是建立安全响应类型:
interface PublicUser {
id: string;
username: string;
}
const publicUser: PublicUser = {
id: user.id,
username: user.username,
};