1. 什么是 BCrypt?
BCrypt 是一种专门用于密码哈希的算法,基于 Blowfish 加密算法设计,由 Niels Provos 和 David Mazières 在 1999 年提出。
它不是通用哈希算法(如 MD5、SHA-1),而是专为密码存储设计的安全哈希函数。
2. 为什么需要 BCrypt?
2.1. 传统哈希的缺陷
传统方式 | 问题 |
| 速度太快,易被暴力破解 |
| 同样太快,彩虹表可破解 |
明文存储 | ❌ 完全不安全 |
传统哈希的致命问题:
速度快 → 攻击者每秒可尝试数亿次
无盐(salt) → 相同密码 → 相同哈希 → 彩虹表直接查
固定强度 → 硬件升级后更容易破解
2.2. BCrypt 的三大核心优势
特性 | 说明 |
自动加盐(Salt) | 每次生成随机 salt,防止彩虹表攻击 |
故意慢(Slow Hash) | 可配置计算强度(cost factor),防止暴力破解 |
自包含(Self-contained) | salt 和 hash 都编码在同一个字符串中 |
3. BCrypt 密码格式详解
一个标准的 BCrypt 密码长这样:
$2a$10$76rfsU6ZGuEhPqDikcSOKuRzTjHpgu1osINW/8lzsPa7nBPe91Ozm
3.1. 拆解结构:
$2a$10$76rfsU6ZGuEhPqDikcSOKuRzTjHpgu1osINW/8lzsPa7nBPe91Ozm │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ 22字符 salt + 31字符 hash(共53字符) │ │ │ └── Cost Factor(强度因子,10 表示 2^10 次迭代) │ └──── Algorithm Version($2a$, $2b$, $2y$)
3.2. 各部分说明
部分 | 含义 |
| BCrypt 算法版本( |
| Cost Factor(成本因子),表示 |
| 22 字符的随机 salt(Base64 编码) |
| 31 字符的哈希值(也含 salt) |
注意:BCrypt 使用的是 Modified Base64 编码,字符集为 ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
,所以 /
是合法字符。
4. BCrypt 的工作原理
4.1. 加密过程(注册/修改密码)
String hashed = BCrypt.hashpw("明文密码", BCrypt.gensalt());
步骤:
生成一个随机 salt(16 字节)
使用 salt 和 password 进行多次 Blowfish 加密(次数由 cost factor 决定)
将算法版本、cost、salt、hash 拼接成
$2a$...
格式输出
4.2. 验证过程(登录)
boolean isMatch = BCrypt.checkpw("用户输入", "数据库存储的$2a$...");
步骤:
从存储的字符串中提取出 salt 和 cost
用提取的 salt 对用户输入的明文进行同样强度的哈希
比较结果是否一致
开发者不需要手动处理 salt!
5. Java 中使用 BCrypt(Spring Security)
5.1. 添加依赖(Spring Boot 已包含)
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> </dependency>
5.2. 配置 PasswordEncoder
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 默认 cost = 10 }
或自定义强度:
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // cost = 12,更慢更安全 }
5.3. 使用示例
@Autowired private PasswordEncoder passwordEncoder; // 注册时加密 String rawPassword = "123456"; String encoded = passwordEncoder.encode(rawPassword); // 存入数据库:$2a$10$76rfsU6ZGuEhPqDikcSOKuRzTjHpgu1osINW/8lzsPa7nBPe91Ozm // 登录时验证 boolean isMatch = passwordEncoder.matches("123456", encoded); // true
6. BCrypt 的安全性优势
攻击类型 | BCrypt 是否防御 | 说明 |
彩虹表攻击 | ✅ | 每个密码 salt 不同,彩虹表失效 |
暴力破解 | ✅ | 故意慢,每秒只能尝试几十次 |
字典攻击 | ✅ | 结合 salt 和慢哈希,极大增加成本 |
相同密码暴露 | ✅ | 即使密码相同,哈希也不同 |
7. Cost Factor(强度因子)选择建议
Cost | 迭代次数 | 推荐场景 |
4 | 16 | 太快,不安全 |
8 | 256 | 勉强可用 |
10 | 1,024 | Spring 默认,推荐 |
12 | 4,096 | 更安全,适合高安全场景 |
14 | 16,384 | 太慢,可能影响用户体验 |
建议:生产环境使用 10~12
测试你的服务器性能:
long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { passwordEncoder.encode("test"); } System.out.println("10次加密耗时: " + (System.currentTimeMillis() - start) + "ms");
理想值:每次加密 30~100ms
8. 常见问题解答
8.1. 为什么每次加密结果不同?
因为每次生成的 salt 不同,但 matches()
方法能正确验证。
8.2. 数据库需要单独存 salt 吗?
不需要! salt 已包含在 $2a$...
字符串中。
8.3. 能和其他算法共存吗?
✅ 可以!使用 DelegatingPasswordEncoder
:
Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put("bcrypt", new BCryptPasswordEncoder()); encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); return new DelegatingPasswordEncoder("bcrypt", encoders);
8.4. BCrypt 有长度限制吗?
是的!BCrypt 只处理前 72 个字节。
如果密码太长(如 >72 字符),后面的字符会被忽略
建议前端限制密码长度(如 64 位以内)
9. 与其他算法对比
算法 | 速度 | 是否加盐 | 推荐用于密码? |
MD5 | 极快 | ❌ | ❌ 绝对不要 |
SHA-256 | 快 | ❌ | ❌ 不适合 |
BCrypt | 慢 | ✅ | ✅ 推荐 |
PBKDF2 | 慢 | ✅ | ✅ 推荐 |
Argon2 | 慢 | ✅ | ✅ 最新推荐(赢得密码哈希竞赛) |
Argon2 是目前最安全的,但 BCrypt 仍是主流选择。
10. 总结
项目 | 说明 |
用途 | 专门用于密码哈希 |
特点 | 自动加盐、故意慢、自包含 |
格式 |
|
Java 使用 |
|
安全性 | 高,抗彩虹表和暴力破解 |
建议 cost | 10~12 |
注意 | 不要存明文,不要手动处理 salt |
0条评论
点击登录参与评论