
Bcrypt密码哈希:为什么重要以及如何使用
📷 Pixabay / PexelsBcrypt密码哈希:为什么重要以及如何使用
以SHA-256哈希存储的密码可以在几分钟内被破解。Bcrypt被设计为故意缓慢——这正是关键所在。了解bcrypt的工作原理、如何选择成本因子,以及如何在Node.js、Python和PHP中实现它。
密码存储问题
想象你正在构建一个登录系统。用户用密码hunter2创建了一个账户。你需要在数据库中存储一些东西,以便他们下周登录时可以验证他们输入了正确的内容。
最简单的解决方案是直接存储hunter2。这是灾难性的。任何数据库泄露、SQL注入、配置错误的S3存储桶中的备份——攻击者现在拥有了每个用户的实际密码。
下一步是哈希它。SHA-256("hunter2")给你一个固定长度的字符串,你无法反转它。存储这个代替。更好了,对吗?
更好——但还不够好。
问题是SHA-256被设计为快速。现代GPU每秒可以计算数十亿个SHA-256哈希。拥有SHA-256哈希密码数据库和好GPU的攻击者,可以使用预计算表(彩虹表)或字典攻击,在几小时甚至几分钟内破解大量常见密码。
Bcrypt专门为解决这个问题而设计。它故意缓慢,速度可以配置。
你可以使用我们的Bcrypt Hash Generator直接实验bcrypt哈希——无需任何设置。
Bcrypt实际上是什么
Bcrypt是1999年由Niels Provos和David Mazieres基于Blowfish密码设计的密码哈希函数。与SHA-256或MD5(通用密码哈希函数)不同,bcrypt是专门为密码存储从头构建的。
三个特性使它非常适合这个用途:
1. 故意设计为缓慢
Bcrypt包含一个可配置的成本因子(也称为工作因子或盐值轮数)。该函数在内部执行2^成本次迭代。成本因子每增加1,计算时间就翻倍。这意味着你可以调整速度以匹配你的硬件——随着硬件变快,你可以增加成本因子来保持领先。
SHA-256哈希需要微秒级时间。成本12的bcrypt哈希大约需要200-300毫秒。这个差异听起来很小,但它极大地改变了攻击者的经济性。
2. 自动处理盐值
盐值是在哈希之前添加到密码中的随机值。加盐确保两个拥有相同密码的用户得到不同的哈希,并且防止预计算的彩虹表攻击。
Bcrypt自动生成密码学安全的128位随机盐值,并将其嵌入输出哈希中。你不需要自己管理盐值。
3. 哈希是自包含的
bcrypt的输出包含算法版本、成本因子、盐值和哈希——全部在一个字符串中。这意味着每个用户只需存储一个字符串,无需单独检索盐值即可验证密码。
典型的bcrypt哈希如下所示:
$2b$12$LJ3m6gEwO/fSFqCVXWLwOeR/dYtTVkRDCwoGLBE0Fg6voFEOB5viy
分解如下:
$2b$ -- 算法版本(2b是当前标准)
12$ -- 成本因子(2^12 = 4,096次密钥调度迭代)
LJ3m6gEwO/fSFqCVXWLwOe -- 22字符base64编码的盐值(128位)
R/dYtTVkRDCwoGLBE0Fg6voFEOB5viy -- 31字符base64编码的哈希
哈希与加密:关键区别
这个区别值得明确阐述。
哈希是单向函数。你输入密码,得到哈希。没有密钥,没有反向操作。检查密码是否匹配哈希的唯一方法是对候选密码进行哈希并比较结果。
加密是双向函数。你用密钥加密数据,可以用相同(或相关)的密钥将其解密回原始内容。
密码应该始终进行哈希处理,而不是加密。如果你加密密码,你的系统某处包含解密密钥,获得该密钥的人就拥有了所有用户的密码。使用哈希,数据库泄露暴露的是哈希而非密码——使用bcrypt,那些哈希很难破解。
成本因子:选择合适的缓慢程度
成本因子直接控制bcrypt的计算量。每次增量都会使工作量翻倍。
| 成本因子 | 迭代次数 | 大约时间(典型服务器) |
|---|---|---|
| 10 | 1,024 | ~65ms |
| 11 | 2,048 | ~130ms |
| 12 | 4,096 | ~250ms |
| 13 | 8,192 | ~500ms |
| 14 | 16,384 | ~1,000ms |
以上时间因硬件而有很大差异。在决定之前,请在实际生产硬件上运行基准测试。
OWASP的当前建议是最低成本因子10,目标哈希时间100ms或更长。大多数从业者今天使用12作为合理的默认值。
权衡:
- 太低(8及以下):哈希速度足够快,拥有好GPU的攻击者可以快速破解泄露的数据库。
- 太高(15+):合法登录请求每次超过一秒,用户会注意到,如果攻击者攻击你的登录端点可能造成拒绝服务。
- 适中范围(~12):每次哈希~250ms足够慢以显著阻碍攻击者,足够快以至用户不会注意到。
Bcrypt实现:代码示例
Node.js
import bcrypt from 'bcrypt';
const COST_FACTOR = 12;
// 哈希密码
async function hashPassword(plaintext) {
const hash = await bcrypt.hash(plaintext, COST_FACTOR);
return hash;
// "$2b$12$..." -- 将此字符串存储在数据库中
}
// 登录时验证密码
async function verifyPassword(plaintext, storedHash) {
const match = await bcrypt.compare(plaintext, storedHash);
return match; // true 或 false
}
// 使用示例
const hash = await hashPassword('hunter2');
console.log(hash); // $2b$12$...
const valid = await verifyPassword('hunter2', hash);
console.log(valid); // true
Python(使用bcrypt包)
import bcrypt
COST_FACTOR = 12
def hash_password(plaintext: str) -> str:
password_bytes = plaintext.encode('utf-8')
salt = bcrypt.gensalt(rounds=COST_FACTOR)
hashed = bcrypt.hashpw(password_bytes, salt)
return hashed.decode('utf-8')
def verify_password(plaintext: str, stored_hash: str) -> bool:
password_bytes = plaintext.encode('utf-8')
hash_bytes = stored_hash.encode('utf-8')
return bcrypt.checkpw(password_bytes, hash_bytes)
PHP
<?php
function hashPassword(string $plaintext): string {
return password_hash($plaintext, PASSWORD_BCRYPT, ['cost' => 12]);
}
function verifyPassword(string $plaintext, string $storedHash): bool {
return password_verify($plaintext, $storedHash);
}
$hash = hashPassword('hunter2');
var_dump(verifyPassword('hunter2', $hash)); // bool(true)
?>
常见错误及如何避免
以明文或可逆编码存储密码
Base64是编码,不是哈希。base64("hunter2")是aHVudGVyMg==,攻击者一行代码就能解码。
对密码使用快速哈希函数
SHA-256、SHA-512、MD5、SHA-1——这些都被设计为快速。快速对密码哈希来说是错误的特性。
72字节截断问题
原始bcrypt规范只处理输入的前72字节。标准解决方法是先用SHA-256哈希输入,然后用bcrypt哈希结果:
import crypto from 'crypto';
import bcrypt from 'bcrypt';
async function hashLongPassword(plaintext) {
const prehashed = crypto
.createHash('sha256')
.update(plaintext)
.digest('base64');
return bcrypt.hash(prehashed, 12);
}
测试Bcrypt哈希
我们的Bcrypt Hash Generator适用于:
- 在开发过程中无需设置项目即可生成测试哈希
- 验证你的实现产生有效的bcrypt输出
- 快速测试代码中的哈希验证
该工具完全在浏览器中运行,从不向服务器发送任何数据。
结论
密码哈希是"工作"和"正确工作"之间的差异非常重要的领域。Bcrypt提供三个重要的东西:故意的缓慢、自动盐化和自包含的输出格式。它不是最新的选项,但经过充分测试、被广泛支持且是正确的选择。
使用成本因子12作为起点。在实际硬件上进行基准测试。如果服务器哈希速度明显快于200ms,则提高因子。如果从头开始,可以考虑Argon2id。
相关工具和资源
- Bcrypt Hash Generator -- 在浏览器中生成和验证bcrypt哈希
- Password Generator -- 生成强随机密码
- Password Strength Checker -- 评估密码强度
- Hash Generator -- 生成MD5、SHA-1、SHA-256和SHA-512哈希
- Hash Functions Explained -- SHA-256、MD5和密码哈希的深度解析