环企首页
bcrypt / argon2
概述
密码哈希是后端安全的基础设施——永远不能明文存储密码。
| 库 | 算法 | 特点 | 建议 |
|---|---|---|---|
| bcrypt | bcrypt | 最广泛使用,兼容性好,成熟稳定 | 首选,几乎所有项目可用 |
| argon2 | argon2id | 2015 年 PHC 冠军,内存硬度高 | 对安全性要求极高的场景 |
核心原则:
- 使用哈希算法(单向),不能解密
- 每次哈希结果不同(因包含随机盐值)
- 验证时用同一算法对比哈希值
bcrypt 常用函数
bcrypt.hash(password, saltRounds)
将明文密码哈希化。
const bcrypt = require('bcrypt')
// 同步用法(推荐,代码简洁)
const hash = bcrypt.hashSync('myPassword123', 10)
// 异步用法(推荐用于生产,高并发时不阻塞)
const hash = await bcrypt.hash('myPassword123', 10)
// saltRounds(轮数)说明:
// 10 = 约 100ms(平衡安全与性能)
// 12 = 约 400ms(更安全)
// 14+ = 很慢(>1s),用于极高安全要求
// 验证结果
console.log(hash)
// $2b$10$N9qo8uLOickgx2ZMRZoMye... (包含算法版本+salt+哈希值)
bcrypt.compare(password, hash)
验证密码是否正确(常用于登录)。
// 同步
const isMatch = bcrypt.compareSync('myPassword123', hash)
// 异步
const isMatch = await bcrypt.compare('myPassword123', hash)
// 典型登录流程
app.post('/login', async (req, res) => {
const { username, password } = req.body
const user = await db.findUser(username)
if (!user) {
return res.status(401).json({ error: '用户名或密码错误' })
}
const isValid = await bcrypt.compare(password, user.passwordHash)
if (!isValid) {
return res.status(401).json({ error: '用户名或密码错误' })
}
// 生成 JWT / Session
res.json({ success: true })
})
bcrypt.genSalt(saltRounds)
手动生成盐值(一般不用,hash 会自动处理)。
const salt = bcrypt.genSaltSync(10)
const hash = bcrypt.hashSync('password', salt)
bcrypt.getRounds(hash)
从哈希值中提取轮数(调试用)。
const rounds = bcrypt.getRounds('$2b$10$...') // 10
argon2 常用函数
argon2.hash(password, options)
const argon2 = require('argon2')
// 异步哈希
const hash = await argon2.hash('myPassword123', {
type: argon2.argon2id, // 推荐:抗旁路攻击+抗 GPU
// type: argon2.argon2d // 抗硬件加速,不推荐
// type: argon2.argon2i // 抗侧信道,不推荐
memoryCost: 2 ** 16, // 内存消耗 64MB
timeCost: 3, // 迭代次数
parallelism: 1, // 并行度
saltLength: 16, // 盐长度(字节)
hashLength: 32 // 输出哈希长度(字节)
})
// 同步(Node 12+)
const hash = argon2.hashSync('password', { ... })
argon2.verify(hash, password)
const isValid = await argon2.verify(hash, 'myPassword123')
argon2.needsRehash(hash, options)
检查哈希是否需要升级(当算法参数改变时,旧哈希仍有效但可以提示重新设置密码)。
if (await argon2.needsRehash(user.passwordHash, { timeCost: 4 })) {
// 算法升级了,提示用户重新设置密码
}
最佳实践
// 密码注册时
app.post('/register', async (req, res) => {
const { username, password } = req.body
// 1. 基础校验
if (password.length < 8) {
return res.status(400).json({ error: '密码至少8位' })
}
// 2. 哈希密码
const passwordHash = await bcrypt.hash(password, 12)
// 3. 存储
await db.createUser({ username, passwordHash })
res.status(201).json({ success: true })
})
// bcrypt 完整配置
const bcrypt = require('bcrypt')
const SALT_ROUNDS = 12
// 在应用启动时校验环境变量中的 saltRounds
if (process.env.NODE_ENV === 'production') {
console.log(`Using ${SALT_ROUNDS} rounds for password hashing`)
}
// 统一工具函数
const passwordHash = async (password) => bcrypt.hash(password, SALT_ROUNDS)
const passwordVerify = async (password, hash) => bcrypt.compare(password, hash)
module.exports = { passwordHash, passwordVerify }
常见问题
| 问题 | 解决方案 |
|---|---|
| 哈希耗时太长导致请求超时 | 调低 saltRounds,生产环境建议 >= 10 |
| 不同哈希结果无法比对 | bcrypt.compare 用相同的 hash 值比对,不需要相同 salt |
| 忘了明文密码 | 无法找回,只能让用户重置密码 |
| 用 md5/sha1/sha256 哈希密码 | 绝对不行,必须用 bcrypt/argon2/scrypt |
| 密码包含特殊字符报错 | bcrypt 内部已处理,不需要额外转义 |