• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
desc-2
环企首页

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 内部已处理,不需要额外转义