BCrypt
2026/1/17大约 4 分钟
BCrypt
1. 核心概念
BCrypt 是一种专门为密码存储设计的哈希算法(Hash Algorithm),而非传统的双向加密算法。
- 不可逆性:无法通过密文还原出明文(没有“解密”一说)。
- 自适应性:可以通过调节“强度因子”来增加破解成本,随着计算机算力提升,我们可以让加密变得更慢,从而对抗未来更强的计算机。
2. 为什么 BCrypt 如此安全?(三大原理)
① 随机盐值 (Salt) - 对抗彩虹表
传统 Hash (如 MD5) 的致命弱点是:MD5("123456") 永远等于同一个字符串。黑客可以用“彩虹表”秒查。
- BCrypt 机制:每次加密时,内部会自动生成随机盐。
- 结果:即使两个用户的密码都是
123456,数据库里存的密文也完全不同。
② 慢哈希 (Slow Hash) - 对抗暴力破解
- BCrypt 机制:通过调节
Strength(默认 10),进行 (1024) 次哈希运算。 - 结果:
- 正常用户登录(算一次):0.1 秒(无感)。
- 黑客暴力破解(算 1 亿次):需要 100 多天。
- 以时间换安全,让黑客的破解成本(电费、算力)远超收益。
③ 密文结构 - 柯克霍夫原则
即使黑客拿到了密文,知道了所有细节,依然无法破解。密文结构如下:
$2a$10$N9qo8uLOickgx2ZMRZoMyuIg/89Ak1xsWb9.a.2a2.3d
| 部分 | 值 | 含义 | 安全影响 |
|---|---|---|---|
| 算法标识 | $2a$ | 使用 BCrypt 算法 | 告诉黑客:这里是铜墙铁壁,别妄想用解密 MD5 的方法。 |
| 强度因子 | $10$ | 2^10 次迭代 | 告诉黑客:算一次要很久,暴力破解会让你破产。 |
| 盐值 | N9qo... | 前 22 位 | 即使你知道了盐,也必须针对这个用户单独计算,无法批量破解。 |
| 哈希值 | uIg/... | 剩余部分 | 最终的指纹。 |
3. 代码实现 (Java / Spring Security)
这是基于 Spring Security BCryptPasswordEncoder 封装的通用工具类。
package utils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 密码加密工具类
* 依赖: spring-boot-starter-security
*/
public class PasswordUtils {
/**
* BCrypt 编码器
* strength = 10 (默认值),即进行 1024 次哈希迭代
* 范围 4-31,数值越大,加密越慢,安全性越高
*/
private static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();
/**
* 加密密码 (注册/修改密码时使用)
* * @param rawPassword 用户输入的明文 (如 "123456")
* @return 存入数据库的密文 (如 "$2a$10$...")
*/
public static String encode(String rawPassword) {
if (rawPassword == null || rawPassword.isEmpty()) {
throw new IllegalArgumentException("密码不能为空");
}
return ENCODER.encode(rawPassword);
}
/**
* 校验密码 (登录时使用)
* * @param rawPassword 用户输入的明文
* @param encodedPassword 数据库查出的密文
* @return true=匹配成功
* * 原理:提取 encodedPassword 中的盐,对 rawPassword 进行同样的哈希运算,
* 然后对比生成的结果与 encodedPassword 中的哈希部分是否一致。
*/
public static boolean matches(String rawPassword, String encodedPassword) {
if (rawPassword == null || encodedPassword == null) {
return false;
}
return ENCODER.matches(rawPassword, encodedPassword);
}
}4. 常见疑问 Q&A
Q1: 既然黑客拿到了盐(在密文里),为什么不能破解?
A: 知道盐 不等于 知道密码。
- 盐只是做菜的“佐料”,密码是“食材”。
- 黑客看到了佐料(盐),也知道了做法(算法),但他手里没有食材(密码),依然做不出这道菜(哈希值)。
- 他唯一的办法是拿不同的食材(穷举密码)一个个去试,但因为 BCrypt 故意设计得很慢,这种尝试在数学上是不划算的。
Q2: 为什么 matches 方法不需要我传盐值?
A: 因为 BCrypt 极其聪明地把盐值直接拼在了加密后的字符串里(前 22 位)。校验时,它会自动从数据库取出的字符串里截取盐值使用。
Q3: 既然这么强,还需要限制用户密码格式吗?
A: 非常有必要!
BCrypt 只能防“猜”,防不了“弱口令”。
- 如果用户密码是
123456,黑客字典里第一个就是它,算一次就撞上了。 - 最佳实践:前端 + 后端 (
@Valid) 必须同时校验:密码长度 > 8位,包含大小写字母和数字。
5. 总结
- 不要发明加密算法:永远使用业界标准(BCrypt / Argon2)。
- 不要隐藏加密逻辑:安全不依赖于“隐瞒”,而依赖于数学难题(算力成本)。
- 流程:
- 存:
PasswordUtils.encode("123")-> 存库。 - 取:
PasswordUtils.matches("123", dbPwd)->以此判断 true/false。
