本文介绍 Java 语言实现 BCrypt Hash 的方法。
目录
- BCrypt 简介
- 版本历史
- 散列值解析
- 代码实现
BCrypt
BCryptPasswordEncoder
BCrypt 简介
BCrypt 是由 Niels Provos 和 David Mazières 基于 Blowfish
加密算法设计的密码散列函数。BCrypt 引入加盐的机制以抵御彩虹表攻击。BCrypt 是一种自适应函数,可以通过增加迭代次数使得计算变慢,因此即使算力提升,也可以通过此方法抵御暴力破解。
版本历史
-
$2$
1999 年最原始的 BCrypt 规范定义的前缀是$2$
,这是遵循了 OpenBSD 密码文件中存储密码时使用的模块化加密格式格式:$1$: MD5-based crypt ('md5crypt')
$2$: Blowfish-based crypt ('bcrypt')
$sha1$: SHA-1-based crypt ('sha1crypt')
$5$: SHA-256-based crypt ('sha256crypt')
$6$: SHA-512-based crypt ('sha512crypt')
-
$2a$
最原始的规范中并未定义如何处理非ASCII
码的字符以及如何处理null
终止符。针对这些情况规范进行了修改(进行此更改后,版本更改为$2a$
):- 字符串必须为
UTF-8
编码 - 必须包含空终止符
- 字符串必须为
-
$2x$, $2y$
2011 年 6 月,在 BCrypt 的 PHP 实现crypt_blowfish
中发现了一个 Bug,此版本标记更改仅限于crypt_blowfish
。 -
$2b$
2014 年 2 月在 OpenBSD 实现中发现了一个 Bug,将字符串的长度存储在一个无符号字符(即 8 位字节)中。如果密码长度超过255
个字符,它将溢出并以255
换行。BCrypt 是为 OpenBSD 创建的。 当他们的库中有错误时,他们决定更改版本号。
散列值解析
- BCrypt 计算出的散列值以
$2a
、$2b
或$2y
开头,表明此 HASH 字符串是一个模块化加密格式的 BCrypt 散列值,如:
$2a$10$5JoiLeDDev5v8BcQLoJNf.yKjrs.WpM57Vd7/qs3oBMYLRnE34Kki
$2b$10$vHDNqGQB09PeE7FHTbiNkuzHHJudkvbTrpq8RdLKj7Ugg59dET/4y
$2y$10$Y3MQpVSieTcPDc1BfLLuseGHlBjm93luly/Bu.X6hUyzqplNko2wW
- 散列值的第二部分指明了一个消耗参数,此参数将密钥扩展迭代计数指定为
2
的幂,如上例中的$10
,指明密钥扩展迭代次数是2
的10
次幂。
代码实现
首先,添加 spring-security-core
依赖,commons-lang3
依赖主要为了在单元测试中引用 RandomUtils
类。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
<scope>test</scope>
</dependency>
BCrypt
通过 org.springframework.security.crypto.bcrypt.BCrypt
实现。
package tutorial.java.util;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCrypt;
public class BCryptTest {
@Test
public void testBCrypt() {
String salt = BCrypt.gensalt(RandomUtils.nextInt(10, 12));
String password = "BCrypt Test" + RandomUtils.nextInt(0, 999);
for (int i = 0, length = RandomUtils.nextInt(1, 10); i < length; i++) {
String hash = BCrypt.hashpw(password, salt);
System.out.println(hash);
BCrypt.checkpw(password, hash);
}
}
}
运行结果:
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
$2a$10$nQyKol5giBHis75q2dqLsulyZ8ctSx9q8zzADNszltr5wq8thGcw6
注意:每次计算得到的散列值相同。
BCryptPasswordEncoder
通过 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
实现。
package tutorial.java.util;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptTest {
@Test
public void test() {
String password = "BCrypt Test" + RandomUtils.nextInt(0, 999);
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
String encodeResult = bCryptPasswordEncoder.encode(password);
System.out.println(encodeResult);
Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
}
bCryptPasswordEncoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B);
for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
String encodeResult = bCryptPasswordEncoder.encode(password);
System.out.println(encodeResult);
Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
}
bCryptPasswordEncoder = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2Y);
for (int i = 0, length = RandomUtils.nextInt(1, 6); i <= length; i++) {
String encodeResult = bCryptPasswordEncoder.encode(password);
System.out.println(encodeResult);
Assert.assertTrue(bCryptPasswordEncoder.matches(password, encodeResult));
}
}
}
运行结果:
$2a$10$KGQsOj4V1rmU/ydyYF8.i.5mhk7sn14sUzSQCIu6tD1Z1cLSjDeqW
$2a$10$Xq219cgK3HAQLt6l95PL7.V17x5edBobdh/3X6OZUR7ar2yafMrpy
$2b$10$s60GvG4hjdxbY5IyVoqlHO7KRT4kLzZUGzYYIjTtad4e7OYEWb9Xm
$2b$10$WQEeE.W19F3IyCWOi6R2.OxITO.ScokZO2XXuw9y5ek1Ac8Wu2JO2
$2b$10$A5je.3QPyDf2j3eHNoeP.OT.t.d/iMrkUm2tJaTnFCNBQJNQonKRu
$2b$10$ZU.vIkomWOAgHGYu.RjkDOqeZslrRjwt8aeW49ILux2Bwxlawjw/G
$2b$10$DjAMEv4naapKQI8K02R96OAD.DkEa5heiA3BKABguPm7VkjTJbkFa
$2y$10$Ij9o8DdlZDw6N8PsVms1nuUc0YawE3bYeskvRiGPIAs4ZueWQ2MyO
$2y$10$n4bpgFBgRkgu5QNzx5Tur.B4jliy2O4dE2zy49oAD4522f37PPOAK
注意:每次计算得到的散列值不同。
网友评论