Bcrypt
Bcrypt 是一种自适应的密码哈希算法 (Adaptive Password Hashing Function),由 Niels Provos 和 David Mazières 在 1999 年设计。
“Bcrypt” 这个名字本质上是一个缩写,全称可以理解为 “Blowfish-based crypt”:
B (Blowfish): 这是算法的核心。Blowfish 是一种由 Bruce Schneier 在 1993 年设计的对称分组加密算法 (Symmetric Block Cipher)。在 Bcrypt 中,它被用作哈希计算的“引擎”。施奈尔设计的Blowfish算法用途广泛,意在替代老旧的DES及避免其他算法的问题与限制。
crypt: 指代 UNIX 和类 UNIX 系统中标准的密码散列接口 crypt()。Bcrypt 的设计初衷就是为了提供一种比传统 crypt() 函数(基于 DES 算法)更安全、更抗暴力破解的替代方案。
crypt 这个词源自古希腊语 “kryptos” (κρυπτός),意思是 “隐藏的” (hidden) 或 “秘密的”。
关联词汇: 它与我们现在熟悉的 Cryptography(密码学)、Encryption(加密)、Cryptic(隐晦的)共享同一个词根。
字面含义: 在古老的语境下,它描述的是那种“把东西藏起来不让人看见”的行为。
与普通的 MD5 或 SHA-256 不同,Bcrypt 的核心哲学是:通过牺牲服务器性能,来大幅增加攻击者的破解成本。
Bcrypt 的这些前缀($2a$, $2b$, $2y$, $2x$)并不是加密算法本身(即 Blowfish)的变更,而是 Bcrypt 实现规范的修订版本号。
它们存在的主要原因是:解决在处理 8 位字符(即非 ASCII 字符,如中文、特殊符号)时的实现歧义。
在 Bcrypt 的早期实现中,对于输入字符串中如何处理第 8 位(High Bit)存在实现上的不一致。如果不同系统在进行密钥扩展(Key Schedule)时对这些字符的处理方式不同,那么同一组密码在不同平台上生成的 Hash 结果就会不一致,导致无法验证。
为了修复这个问题并保证跨平台的兼容性,社区推出了以下版本:
| 版本号 | 历史地位与定义 |
|---|---|
| $2a$ | 原始版本。虽然它是 Bcrypt 的鼻祖,但它在处理 8 位字符时存在跨平台实现歧义。 |
| $2x$ | 历史上的补丁。这是为了修复 $2a$ 的特定 bug 而引入的,但它并不是一个被广泛接受的官方标准,主要用于向后兼容特定的旧实现。 |
| $2y$ | 官方修订版。这是 OpenBSD 官方针对 $2a$ 歧义问题的修正版。它确保了对 8 位字符的处理在所有符合规范的系统上结果一致。 |
| $2b$ | 当前行业标准。在 $2y$ 之后,为了进一步消除所有潜在的实现困惑,将其标准化为 $2b$。目前几乎所有现代 Linux 发行版和加密库(如 PHP, Python bcrypt 库)默认都生成 $2b$。 |
- 安全性一致性: 无论是
$2a$、$2y$还是$2b$,它们使用的底层加密算法 Blowfish 是完全一样的。它们的安全性没有区别,区别仅在于“如何将你的字符串转换成密钥”这一步骤的编码细节。 - 兼容性 (Verification): Bcrypt 的验证函数非常聪明。当你调用验证函数(如
password_verify或类似的库)时,它会读取 Hash 前缀的 ID(如$2b$),然后使用对应的算法逻辑来校验密码。这意味着$2b$的库完全可以校验$2a$格式的旧 Hash,反之亦然。 - 建议: 如果你正在部署新的服务或重置数据库中的密码,请强制使用
$2b$。这是目前最标准、最具跨平台互操作性的实现。
Linux crypt 标识符参考表
| 标识符 (ID) | 算法名称 (Algorithm) | 安全等级 | 常见场景 / 备注 |
|---|---|---|---|
$1$ |
MD5 | ❌ 极不安全 | 经典的 Linux 传统格式,已过时。 |
$2a$ |
Bcrypt | ✅ 推荐 | 早期 Bcrypt 实现,存在 8 位字符歧义。 |
$2b$ |
Bcrypt | ✅ 推荐 | 当前 Bcrypt 的标准版本,修复了实现差异。 |
$2y$ |
Bcrypt | ✅ 推荐 | Bcrypt 的另一种标准修正版。 |
$5$ |
SHA-256 | ⚠️ 一般 | Linux 较新的默认标准,比 MD5 强,但不及 Bcrypt/Yescrypt。 |
$6$ |
SHA-512 | ⚠️ 一般 | 大多数现代企业级 Linux 发行版的默认标准。 |
$7$ |
Scrypt | ✅ 优秀 | 基于内存硬化的算法,非常强悍。 |
$y$ |
Yescrypt | 🚀 顶级 | 目前 Linux 发行版(Fedora/Debian/Ubuntu)最推荐的方案。 |
$apr1$ |
Apache MD5 | ❌ 不安全 | 专用于 Apache HTTP Server 的认证文件。 |
| $s2$ | Scrypt | ||
| $argon2id$ | 它是 IETF RFC 9106 和 OWASP 推荐的默认选择。 |
为什么在 Linux 系统中很少看到 $s2$ (Scrypt)?
虽然 scrypt 非常强,但在 Linux 的 /etc/shadow 中,通常看到的是 $y$ (Yescrypt) 或 $6$ (SHA-512)。原因如下:
Yescrypt 的出现: yescrypt 本质上可以看作是 scrypt 的进化版,它兼容性更好,且在对抗侧信道攻击(side-channel attacks)方面做了优化。现代 Linux 发行版(如 Debian 12, RHEL 9)更倾向于直接原生集成 yescrypt。
标准库支持: scrypt 的参数配置比 bcrypt 复杂得多(需要调整 n, r, p 三个参数),对于配置不当的系统管理员来说,极易造成性能灾难(例如设置了过高的内存占用,导致系统 OOM 宕机)。
apt install argon2 -y
echo -n "myPassword123" | argon2 saltsalt123 -id -t 3 -m 12 -p 4
PHC 字符串格式 (PHC String Format)
在 PHC 之前,算法(如 Bcrypt 的$2b$)非常依赖约定俗成的短标识符。
随着哈希算法变得越来越复杂(例如 Argon2 需要指定内存占用 m、迭代次数 t、并行度 p),简单的 $id$salt$hash 格式已经不够用了。
PHC 制定了通用的存储规范,使得哈希字符串本身就是“自描述”的。
格式模板:
$<id>$<param>=<value>,<param>=<value>$<salt>$<hash>
$<id>$: 算法名称,例如 $argon2id$。
<param>=<value>: 关键参数段,例如 m=65536,t=3,p=4。这直接定义了该哈希在验证时需要消耗多少内存和 CPU。
$<salt>$: 加盐部分。
$<hash>$: 最终的哈希结果。
