bcryptjs详解:从原理到实战,轻松搞定密码安全

17 阅读9分钟

跟大家唠个写代码时必遇到的问题,尤其是做后端、写用户系统的朋友,估计都琢磨过:用户密码到底怎么存才安全?

我刚写代码那会,踩过一个巨坑——直接把用户密码明文存在数据库里。后来被前辈骂醒:要是数据库被黑客攻破,所有用户的密码就全泄露了,轻则用户投诉,重则要担法律责任。

那有人说,我把密码加密一下存进去不就行了?比如用MD5、SHA256这些哈希算法,把密码变成一串乱码。这话没毛病,但不够安全——现在黑客手里有“彩虹表”,就是提前算好各种常见密码的哈希值,一对比就能反推出原始密码。

这时候,bcryptjs就该登场了。它不是什么高深莫测的技术,就是一个专门用来给密码做处理的JavaScript工具库,零依赖,不管是Node.js后端还是浏览器端,都能直接用,上手特别快。

今天就跟大家好好唠唠,把bcryptjs讲透:它到底是啥、为啥安全、怎么用,还有我踩过的那些坑,一次性说清楚,不用你去翻晦涩的官方文档。

先跟大家说下,bcryptjs的核心作用就一个:把用户输的明文密码,变成一串乱七八糟、没法反向破解的“哈希值”,然后把这串哈希值存进数据库就行。

跟MD5那些普通哈希比,它多了两个关键操作,也是它安全的核心:自动加盐慢哈希

先解释下这两个词,别被专业名词吓住,其实特别好懂:

  1. 自动加盐:“盐”就是一串随机字符串,bcryptjs加密的时候,会自动生成一个唯一的随机盐,然后把盐和密码混在一起再做哈希。哪怕两个用户密码完全一样,因为盐不一样,最终的哈希值也不一样——这样就彻底破解了彩虹表,黑客就算拿到哈希值,也没法批量反推密码。

而且最省心的是,不用你手动存盐!bcryptjs会把盐直接嵌在最终的哈希值里,后续验证密码的时候,它自己就能从哈希值里提取盐,不用你额外费心维护盐的存储,省了好多事。

  1. 慢哈希:说白了就是故意放慢加密的速度。你可以设置一个“成本因子”(比如10),这个数值越高,加密时的迭代次数就越多(成本因子10,就是2的10次方=1024次迭代),加密耗时也就越长。

可能有人会问:慢了不是影响性能吗?这正是它的高明之处——对正常用户来说,登录时验证一次密码,哪怕耗时0.1秒,也完全感觉不到;但对黑客来说,暴力破解(一个个试密码)的时候,每试一次都要等0.1秒,试一万次就要等1000秒,成本直接拉满,黑客自然就放弃了。

另外补充一句,bcryptjs是纯JavaScript写的,不需要装任何额外的原生依赖,比另一个类似工具bcrypt(需要装C++依赖,很容易装失败)友好太多,这也是我一直推荐大家用它的原因。

聊完它的核心特点,再跟大家说说工作原理,不用懂源码,记住3个关键逻辑,就知道它是怎么干活的了。

不用深究底层的Blowfish算法,也不用看源码里那些拗口的函数,只要记住这3步:

  1. 加密流程:用户输的明文密码 → 自动生成随机盐 → 结合盐和成本因子,进行多轮哈希运算 → 生成最终的哈希值(里面已包含盐和成本因子)。

举个通俗的例子:你的密码是“123456”,bcryptjs会生成一个随机盐“abcdef”,成本因子设10,然后用这三个东西一起运算,最终生成一串像“2a2a10abcdefxxxxxxxxxxxxxxxxxxxxxx”的哈希值——前面的“abcdefxxxxxxxxxxxxxxxxxxxxxx”的哈希值——前面的“2a1010”是算法标识和成本因子,中间的“abcdef”是盐,后面的就是真正的哈希结果。

  1. 验证流程:用户登录输入明文密码 → 从数据库取出之前存的哈希值 → 从哈希值里提取盐和成本因子 → 用同样的盐和成本因子,对输入的明文密码重新做哈希 → 对比两次哈希值是否一致,一致就是密码正确,不一致就是输错了。

这里的关键是:验证的时候不用再存盐、记成本因子,哈希值里全包含了,调用bcryptjs的方法就能自动完成,特别简单。

  1. 自适应特性:随着电脑算力越来越强,黑客的破解速度也会变快。这时候你只需要提高成本因子,就能让加密速度变慢,始终能压制住黑客——比如几年前用成本因子10,现在可以提到12,破解难度就会翻倍,不用改其他代码,特别灵活。

聊完理论,最关键的就是实战用法,我把步骤拆清楚,代码都给你写好,复制粘贴就能用,一点不麻烦。下面以Node.js项目为例(最常用的场景),一步步跟大家说:

第一步:安装bcryptjs

打开终端,在你的项目里输入命令,跟装其他npm包一样简单:

npm install bcryptjs

第二步:引入bcryptjs

在你的代码文件里,引入它就行,两种方式都可以,看你的项目用哪种模块规范:

// CommonJS(传统Node.js项目)
const bcrypt = require('bcryptjs');
// ES模块(现代项目,比如Vue3、React后端)
import bcrypt from 'bcryptjs';

第三步:核心操作(加密+验证)

bcryptjs有两种用法:同步和异步。推荐用异步,因为加密是耗时操作,同步会阻塞代码,影响项目性能,日常用异步就够了。

先看异步用法(推荐):

// 1. 加密密码(用户注册时用)
async function encryptPassword(plainPassword) {
  // 生成盐,第二个参数就是成本因子,默认10,范围4-31
  const salt = await bcrypt.genSalt(10);
  // 用盐加密明文密码,返回哈希值
  const hashedPassword = await bcrypt.hash(plainPassword, salt);
  return hashedPassword; // 把这个哈希值存进数据库
}
// 2. 验证密码(用户登录时用)
async function verifyPassword(plainPassword, hashedPassword) {
  // 对比明文密码和数据库里的哈希值,返回true/false
  const isMatch = await bcrypt.compare(plainPassword, hashedPassword);
  return isMatch; // true=密码正确,false=密码错误
}

举个实际场景的例子(注册+登录),大家一看就懂:

// 模拟用户注册
const userPassword = '123456'; // 用户输入的明文密码
const hashedPwd = await encryptPassword(userPassword);
// 把hashedPwd存进数据库,比如存在user表的password字段

// 模拟用户登录
const loginPassword = '123456'; // 用户登录时输入的密码
const storedHashedPwd = '从数据库取出的哈希值'; // 比如$2a$10$abcdefxxxxxxxxxxxxxxxxxxxxxx
const isLoginSuccess = await verifyPassword(loginPassword, storedHashedPwd);
if (isLoginSuccess) {
  console.log('登录成功');
} else {
  console.log('密码错误');
}

如果是简单场景,比如测试的时候,也可以用同步用法,代码更简洁,但不推荐在正式项目里用:

// 同步加密
const salt = bcrypt.genSaltSync(10);
const hashedPassword = bcrypt.hashSync(userPassword, salt);
// 同步验证
const isMatch = bcrypt.compareSync(loginPassword, storedHashedPwd);

另外补充两个常用的小方法,偶尔会用到,记一下就行:

// 从哈希值中提取加密轮次(成本因子对应的迭代次数)
const rounds = bcrypt.getRounds(hashedPassword);
// 从哈希值中提取盐值
const saltFromHash = bcrypt.getSalt(hashedPassword);

聊完用法,再跟大家说说我踩过的坑,总结了5个最常见的,大家避开这些坑,能少走很多弯路。

  1. 手动管理盐值:完全没必要!bcryptjs会自动生成盐、自动嵌入哈希值,你手动存盐、传盐,反而容易出错,纯属多此一举。

  2. 成本因子设太高或太低:太低(比如4),加密太快,不安全;太高(比如20),加密耗时太长,会导致用户登录卡顿,甚至服务器负载过高。日常默认设10就够了,后续根据服务器性能调整。

  3. 密码长度超过72字节:bcryptjs有个限制,明文密码最多只能处理72字节(中文一个占3字节,大概24个中文),超过的部分会被自动截断,导致验证失败。解决办法很简单:要么限制用户密码长度,要么先对密码做一次SHA256哈希,再用bcryptjs加密。

  4. 混淆“加密”和“哈希”:跟大家说个小细节,别再说“用bcryptjs加密密码”了,严格来说,它是“哈希”,不是“加密”——加密是可以反向解密的,而哈希是单向的,没法反向推出原始密码,这也是它安全的核心。

  5. 日志打印哈希值:有些朋友调试的时候,会把哈希值打印到日志里,这是风险操作!如果日志泄露,黑客还是能拿到哈希值,虽然破解难度大,但多一事不如少一事,调试完记得删掉日志打印代码。

最后跟大家总结一下,bcryptjs到底值不值得用?答案很明确:值得,而且日常用它完全够用。

它不需要你懂复杂的加密原理,API简单,零依赖,上手快;自动加盐、慢哈希、自适应特性,安全性拉满,能抵御大部分密码破解攻击;不管是小项目还是大型系统,都能直接用。

其实密码安全没有那么复杂,不用追求高深的加密技术,只要记住:永远不要存明文密码,用bcryptjs做哈希处理,成本因子设10左右,避开上面的坑,就能满足绝大多数项目的密码安全需求。

如果你的项目还在存明文密码,或者用MD5哈希,赶紧换成bcryptjs,几分钟就能搞定,换来的是用户数据的安全,也能让自己少担一份风险。

最后留个小话题:你用bcryptjs的时候,踩过什么坑?欢迎在评论区交流~