2025-10-13 16:21

BCrypt 加密详解

王姐姐

Java后端

(37)

(0)

收藏

1. 什么是 BCrypt?

BCrypt 是一种专门用于密码哈希的算法,基于 Blowfish 加密算法设计,由 Niels Provos 和 David Mazières 在 1999 年提出。

它不是通用哈希算法(如 MD5、SHA-1),而是专为密码存储设计的安全哈希函数

2. 为什么需要 BCrypt?

2.1. 传统哈希的缺陷

传统方式

问题

MD5(password)

速度太快,易被暴力破解

SHA-256(password)

同样太快,彩虹表可破解

明文存储

❌ 完全不安全

传统哈希的致命问题:

  1. 速度快 → 攻击者每秒可尝试数亿次

  2. 无盐(salt) → 相同密码 → 相同哈希 → 彩虹表直接查

  3. 固定强度 → 硬件升级后更容易破解

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. 各部分说明

部分

含义

$2a$

BCrypt 算法版本(2a, 2b, 2y 都是常见变种)

10

Cost Factor(成本因子),表示 2^10 = 1024 次加密循环

76rfsU6ZGuEhPqDikcSOKu

22 字符的随机 salt(Base64 编码)

RzTjHpgu1osINW/8lzsPa7nBPe91Ozm

31 字符的哈希值(也含 salt)

注意:BCrypt 使用的是 Modified Base64 编码,字符集为 ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,所以 / 是合法字符。

4. BCrypt 的工作原理

4.1. 加密过程(注册/修改密码)

String hashed = BCrypt.hashpw("明文密码", BCrypt.gensalt());

步骤:

  1. 生成一个随机 salt(16 字节)

  2. 使用 salt 和 password 进行多次 Blowfish 加密(次数由 cost factor 决定)

  3. 将算法版本、cost、salt、hash 拼接成 $2a$... 格式输出

4.2. 验证过程(登录)

boolean isMatch = BCrypt.checkpw("用户输入", "数据库存储的$2a$...");

步骤:

  1. 从存储的字符串中提取出 salt 和 cost

  2. 用提取的 salt 对用户输入的明文进行同样强度的哈希

  3. 比较结果是否一致

开发者不需要手动处理 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. 总结

项目

说明

用途

专门用于密码哈希

特点

自动加盐、故意慢、自包含

格式

$2a$10$salt+hash

Java 使用

BCryptPasswordEncoder

安全性

高,抗彩虹表和暴力破解

建议 cost

10~12

注意

不要存明文,不要手动处理 salt


0条评论

点击登录参与评论