一、注册流程(Register / Sign-up)
-
用户输入密码
- 用户在注册页面输入密码,例如
"MyP@ssw0rd!"。
- 用户在注册页面输入密码,例如
-
后端生成哈希
-
后端接收到明文密码后,调用 bcrypt:
1 2const bcrypt = require("bcryptjs"); const hash = await bcrypt.hash(password, 12); // 12 是 cost -
bcrypt 会自动:
- 生成随机盐(每个用户唯一)
- 把盐和密码一起进行加密运算
- 输出最终哈希(包含版本、成本因子、盐和密码哈希)
-
-
存储数据库
-
数据库只存 哈希字符串,不存明文密码,也不需要单独存盐:
1 2 3 4{ "username": "alice", "passwordHash": "$2b$12$CwTycUXWue0Thq9StjUM0u1Cq0sZ3L4y9yVb0z0s2cR4tH6B5mCq2" } -
这样即使数据库被泄露,攻击者也无法直接获得用户密码。
-
二、登录流程(Login / Sign-in)
-
用户提交凭证
- 用户输入邮箱/用户名和密码(明文),例如
"MyP@ssw0rd!"。
- 用户输入邮箱/用户名和密码(明文),例如
-
查找用户数据
- 后端根据用户名或邮箱查询数据库,取出
passwordHash。
- 后端根据用户名或邮箱查询数据库,取出
-
验证密码
-
调用 bcrypt.compare:
1const ok = await bcrypt.compare(plainPassword, storedHash); -
bcrypt 内部执行:
-
解析哈希中的盐、版本和 cost
- 前 29 个字符包含算法版本、成本因子和盐
- 例如
$2b$12$CwTycUXWue0Thq9StjUM0u
-
用同样的盐和 cost 对用户输入的明文密码重新计算哈希
-
比较计算结果与存储的哈希是否相同
-
-
-
判断结果
- 如果相同 → 登录成功,创建会话 / JWT 等
- 如果不同 → 登录失败,返回统一错误提示(不要暴露“用户不存在”或“密码错误”的差异)
三、bcrypt 的安全机制体现
| 特性 | 在注册/登录流程中体现 |
|---|---|
| 随机盐 | 每个用户的哈希都不一样,即使密码相同 |
| 可调成本 | cost 控制哈希计算耗时,增加暴力破解难度 |
| 不可逆 | 数据库里的哈希无法还原密码 |
| 内嵌盐 | 登录时无需单独存储盐,compare 会自动解析 |
| 慢哈希 | 阻止攻击者批量暴力破解 |
四、流程图(文字版)
|
|
五、工程实践小提示
- 异步接口:
bcrypt.hash/bcrypt.compare使用异步,避免阻塞。 - 统一错误信息:防止信息泄露。
- 可静默升级成本:登录时检查成本,如果低于新目标 cost,可重新哈希更新数据库。
- 防暴力:加速限制、账户锁定、2FA。
- 密码长度注意:bcrypt 只处理前 72 字节,超长密码可先做 SHA256 压缩(可选,但要明确定义)。
✅ 总结: 从用户角度看,注册时密码被安全地加密存储,登录时 bcrypt 会自动解析哈希中的盐并验证密码。 用户只需要输入密码,整个盐、成本和哈希的复杂处理都由后端和 bcrypt 库完成。
如果你愿意,我可以画一张 带盐解析细节的可视化流程图,让注册和登录的 bcrypt 哈希流程一目了然。
你想让我画吗?