加密哈希函数 BCrypt
bcrypt 是由 Niels Provos 和 David Mazières 设计的密码散列函数,基于 Blowfish 密码,于 1999 年在 USENIX 上提出。 除了加入盐来防止彩虹表攻击外,bcrypt 还是一种自适应函数:随着时间的推移,计算机硬件的计算能力会越来越高,它可以通过增加迭代计数使其计算过程变慢,以此抵消硬件性能的提升,使其仍然可以抵抗暴力搜索破解攻击。
bcrypt 函数是 OpenBSD的默认密码哈希算法,也是某些 Linux 发行版(例如 SUSE Linux)的默认算法,在绝大部分流行的编程语言中,都有具体实现。
背景
Blowfish算法 在分组密码中因其昂贵的密钥设置阶段而引人注目。它首先使用标准状态的子密钥,然后使用该状态的一部分密钥执行块加密,并使用该加密的结果(更准确地说是散列)来替换一些子密钥。然后它使用这种修改后的状态来加密密钥的另一部分,并使用结果替换更多的子密钥。它以这种方式进行,使用逐渐修改的状态来散列密钥并替换状态位,直到设置所有子密钥为止。
Provos和Mazières充分利用了这一点并更进一步。他们为Blowfish开发了一种新的密钥设置算法,并将由此产生的密码称为“Eksblowfish”(意为“昂贵的密钥调度Blowfish”)。密钥设置从修改后的标准Blowfish密钥设置开始,利用salt(盐值)和密码来设置所有子密钥。之后进行多轮迭代,在每一轮中交替使用salt和密码作为密钥应用标准的Blowfish密钥编排算法,每一轮都以上一轮的子密钥状态为初始状态。理论上,这个方法并不比标准的Blowfish密钥调度更强,但是重设密钥的轮数可以自由配置。因此,该过程可以被设置为任意缓慢,有助于阻止对哈希值或salt的暴力破解攻击。
描述
bcrypt函数的输入是密码字符串(最高72字节)、数字表示的成本和16字节(128位)的盐值。盐值通常是随机值。bcrypt函数使用这些输入来计算24字节(192位)哈希值。bcrypt函数的最终输出是以下形式的字符串:
1 | $2<a/b/x/y>$[cost]$[22 character salt][31 character hash] |
例如,输入密码 abc123xyz
、成本 12
和随机盐值,bcrypt的输出字符串为:
1 | $2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW |
解释:
$2a$
:哈希算法标识符(bcrypt)
12
:输入成本(2 12 即 4096 轮)
R9h/cIPz0gi.URNNX3kh2O
:输入盐的 Base-64 编码
PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
:计算出的 24 字节哈希值的前 23 字节的 Base-64 编码
bcrypt 中的 base-64 编码使用表
./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
,这与 RFC 4648 Base64 编码不同。
版本历史
$2$ (1999)
最初的 bcrypt 规范定义了 $2$
前缀。这遵循在 OpenBSD 密码文件中存储密码时使用的模块化加密格式格式
$1$
:基于 MD5 的 crypt (‘md5crypt’)$2$
:基于 Blowfish 的 crypt(’bcrypt’)$sha1$
:基于 SHA-1 的 crypt(’sha1crypt’)$5$
:基于 SHA-256 的加密(’sha256crypt’)$6$
:基于 SHA-512 的 crypt(’sha512crypt’)
$2a$
最初的规范中没有定义如何处理非ASCII字符,也没有定义如何处理 null
终止符。$2a$ 规范经过修订,指定在对字符串进行哈希处理时:
- 字符串必须是 UTF-8 编码
- 必须包含
null
终止符
$2x$、$2y$(2011 年 6 月)
在2011年6月,人们在crypt_blowfish(bcrypt算法的一个PHP实现)中发现了一个bug。这个bug导致对第8位(bit)被置为1的字符处理不当。为了解决这个问题,有人建议系统管理员更新现有的密码数据库,将”2a”替换为”2x”,以此来标记那些有问题的哈希值(需要使用旧的有缺陷的算法进行处理)。同时他们还建议,让crypt_blowfish对于用修复后的算法生成的哈希值,输出 $2y$
。
除了crypt_blowfish之外,包括Canonical和OpenBSD在内的其他机构都没有采用2x/2y这种版本标记方式。这种版本标记更改仅限于crypt_blowfish。
$2b$ (2014 年 2月)
人们在OpenBSD对于bcrypt的实现中发现了一个bug。这个bug使用了一个无符号的8位值来保存密码的长度。对于超过255个字节的密码,密码不会被截断到72个字节,而是会被截断到72或者长度对256取模后的值(两者中更小的那个)。例如,一个260字节的密码会被截断到4个字节,而不是72个字节。
bcrypt最开始是为OpenBSD开发的。当他们的库中出现bug时,他们决定增加版本号。
Java中使用
依赖
pom.xml
1 | <dependency> |
主程序
1 | public static void main(String[] args) { |
输出如下:
1 | 加密后的密码:$2a$10$WbhwIb1/dnE/nDkLeEco3uH0ZlPYwaEC3sGSU68K5aQRj7POA9dsS |
说明
生成随机盐:BCrypt.gensalt()
生成密码哈希:BCrypt.hashpw(password, salt)
验证密码:BCrypt.checkpw(password, passwordHash)