正在加载,请稍候…

Bcrypt 密码哈希:开发者完整指南

了解为什么 bcrypt 是密码存储的黄金标准,成本因子如何工作,以及如何安全地验证密码。

Bcrypt 密码哈希:开发者完整指南

什么是 bcrypt?

bcrypt 是由 Niels Provos 和 David Mazieres 设计的密码哈希函数,在 1999 年的 USENIX 会议上提出。它仍然是用户认证系统中被广泛推荐的密码哈希算法之一。与 MD5 或 SHA-256 等通用哈希函数(设计为快速)不同,bcrypt 故意设计为慢速且计算密集——这是密码安全的关键属性。

Bcrypt 密码哈希:开发者完整指南 插图

为什么密码要使用 bcrypt?

快速哈希函数的问题

像 SHA-256 这样的通用加密哈希函数针对速度进行了优化。现代硬件每秒可以计算数十亿个 SHA-256 哈希。这使得对哈希密码的暴力破解攻击变得可行:

  • GPU 每秒可检查约 100 亿个 MD5 哈希
  • 如果 SHA-256 哈希密码的数据库泄露,攻击者每秒可以测试数十亿个猜测

bcrypt 的自适应成本因子

bcrypt 包含一个工作因子(也称为成本因子),用于控制哈希计算的计算成本。每次递增使工作量加倍:

  • 工作因子 10:约 100ms 每个哈希
  • 工作因子 12:约 400ms 每个哈希
  • 工作因子 14:约 1.6 秒每个哈希

攻击者在 GPU 上破解工作因子 12 的 bcrypt 哈希时,每秒只能测试约 2.5 个哈希(而 MD5 可达数十亿)。这使得对强密码的暴力破解攻击在计算上不可行。

随着硬件速度提升,你可以增加工作因子以保持安全性。这种自适应特性是 bcrypt 的关键优势。

bcrypt 哈希格式

一个 bcrypt 哈希看起来像:

$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/AwB5RoP8YTp7vFyAu

分解如下:

  • $2b$ — bcrypt 算法版本(2b 是当前推荐的版本)
  • 12 — 工作因子(成本因子)
  • LQv3c1yqBWVHxkd0LHAkCO — 22 字符 Base64 编码的盐(128 位)
  • Yz6TtxMQJqhN8/AwB5RoP8YTp7vFyAu — 31 字符 Base64 编码的哈希

盐和哈希一起存储在单个输出字符串中——你不需要单独的盐列。

bcrypt 加盐过程

是在哈希之前添加到密码中的随机数据。bcrypt 为每个密码生成一个唯一的随机盐:

用户设置密码:"mysecretpassword"
随机盐:        "LQv3c1yqBWVHxkd0LHAkCO"
哈希输入:         salt + password
bcrypt 输出:      $2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/AwB5RoP8YTp7vFyAu

加盐的好处:

  • 防止彩虹表:预计算的哈希表无效,因为每个哈希都有唯一的盐
  • 相同密码产生不同哈希:两个用户使用 "password123" 会得到完全不同的 bcrypt 哈希
  • 无需单独存储盐:盐嵌入在哈希输出中

Bcrypt 密码哈希:开发者完整指南 插图

选择工作因子

OWASP 建议工作因子在服务器硬件上至少需要 1 秒。随着硬件改进,相应增加工作因子。

指南:

  • 交互式登录(Web 应用):10-12(100ms-400ms)
  • API 认证:10-11(可接受的延迟)
  • 离线存储:14+(最大安全性)

测试你的服务器,选择在延迟预算内保持认证的最高工作因子。

流行语言中的 bcrypt

Node.js(bcryptjs 或 bcrypt)

const bcrypt = require('bcryptjs');

// 哈希密码
const saltRounds = 12;
const hash = await bcrypt.hash('myPassword', saltRounds);

// 验证密码
const isValid = await bcrypt.compare('myPassword', hash);

Python(bcrypt 库)

import bcrypt

# 哈希
password_hash = bcrypt.hashpw(b'myPassword', bcrypt.gensalt(rounds=12))

# 验证
is_valid = bcrypt.checkpw(b'myPassword', password_hash)

PHP(内置 password_hash)

// 哈希
$hash = password_hash('myPassword', PASSWORD_BCRYPT, ['cost' => 12]);

// 验证
$valid = password_verify('myPassword', $hash);

Bcrypt 密码哈希:开发者完整指南 插图

bcrypt 的局限性

72 字符密码限制

bcrypt 将输入截断为 72 字节。超过 72 个字符的密码无论额外字符如何,都被视为相同。解决方案:先使用 SHA-256 预哈希,或改用 Argon2id。

不适用于非密码数据

bcrypt 专为密码设计(人类可记忆、中等长度)。对于加密任意数据,请使用 AES。对于 API 密钥验证,bcrypt 过于繁重——HMAC-SHA256 更合适。

替代方案:Argon2、scrypt

更现代的替代方案:

  • Argon2id:密码哈希竞赛(2015)获胜者。更好的内存硬性。推荐用于新系统。
  • scrypt:类似的内存硬性设计,用于某些加密货币系统。

bcrypt 由于其长期记录和广泛的库支持,仍然适用于大多数应用。

使用 bcrypt 工具

我们的工具:

  1. 哈希密码 — 输入任意密码并选择工作因子
  2. 验证哈希 — 测试密码是否匹配现有的 bcrypt 哈希
  3. 配置成本因子 — 设置工作因子从 4(测试)到 14(最大)
  4. 查看计时 — 查看在所选成本因子下哈希所需的时间
  5. 复制结果 — 一键复制生成的哈希

用于测试 bcrypt 实现、为开发数据库生成哈希,以及了解工作因子如何影响性能。