分类
devops

Bcrypt

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$
  1. 安全性一致性: 无论是 $2a$$2y$ 还是 $2b$,它们使用的底层加密算法 Blowfish 是完全一样的。它们的安全性没有区别,区别仅在于“如何将你的字符串转换成密钥”这一步骤的编码细节。
  2. 兼容性 (Verification): Bcrypt 的验证函数非常聪明。当你调用验证函数(如 password_verify 或类似的库)时,它会读取 Hash 前缀的 ID(如 $2b$),然后使用对应的算法逻辑来校验密码。这意味着 $2b$ 的库完全可以校验 $2a$ 格式的旧 Hash,反之亦然。
  3. 建议: 如果你正在部署新的服务或重置数据库中的密码,请强制使用 $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>$: 最终的哈希结果。