先入为主阐述一下Bcrypt编码算法两个优点
- 相同的原文每次编码出来的密文不相同
- 编码的速度很慢
是的这就是Bcrypt的优点,有经验的读者看上去会觉得奇怪,这还算得上是“优点”吗!特别是像编码的速度慢这一点。在这里为了说明这是所谓的“优点”不得不先从用户密码存储安全方面说起:
首先提及MD5编码算法大家马上就会联想到这是一种可以破解的加密算法,破解的方法主要是采用“彩虹表“(不清楚可在网上搜索),上述攻击方法最根本就是相同的原文经过MD5编码之后产生相同的密文,(简单说就是相同密码得出相同的密文,那么反过来就可以推导出原文),有经验的开发者会采用salt和多重MD5编码的方式来避免密文被破解,如果攻击者预先知道或者能够获取到足够多的信息上述两种方式也是很容易被攻破的,说到这里不喻而明Bcrypt第一个优点就是为了解决这种问题。当然MD5是一个相对过时的哈希编码算法,当前就算简单起见也应当选择SHA256等相对安全性更高的哈希编码算法。那第二点为何编码的速度很慢有什么帮助,你想想通过彩虹表攻击之前必需预先准备一套容量足够大的表碰撞表,如果能过MD5算法和Bcrypt作对比,因为Bcrypt足够慢而致依生成碰撞表付出更大量的计算量,这种困难可以使得生成碰撞表变得不现实,所以破解的难度就相应增大,正因为上述的原因所以说Bcrypt更适合作为用户密码加密编码的算法来使用,并且在使用的过程中不涉及多重编码,Salt和Pepper等问题,所以使用它的API使得代码更简洁和易懂。
以下是Bcrypt编码算法的实现,该类是引用自Spring Security模块中的
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
/**
* Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
* can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom
* instance. The larger the strength parameter the more work will have to be done
* (exponentially) to hash the passwords. The default value is 10.
*
* @author Dave Syer
*
*/
public class BCryptPasswordEncoder implements PasswordEncoder {
private Pattern BCRYPT_PATTERN = Pattern
.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
private final Log logger = LogFactory.getLog(getClass());
private final int strength;
private final SecureRandom random;
public BCryptPasswordEncoder() {
this(-1);
}
/**
* @param strength the log rounds to use, between 4 and 31
*/
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
/**
* @param strength the log rounds to use, between 4 and 31
* @param random the secure random instance to use
*
*/
public BCryptPasswordEncoder(int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.strength = strength;
this.random = random;
}
public String encode(CharSequence rawPassword) {
String salt;
if (strength > 0) {
if (random != null) {
salt = BCrypt.gensalt(strength, random);
}
else {
salt = BCrypt.gensalt(strength);
}
}
else {
salt = BCrypt.gensalt();
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password");
return false;
}
if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
logger.warn("Encoded password does not look like BCrypt");
return false;
}
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
}
测试的代码如下:
@Test
public void testBCryptPasswordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(6);
for (int i = 0; i < 3; i++) {
String encodedText = bCryptPasswordEncoder.encode("567123TR");
System.out.println(String.format("第%d次编码的结果:%s", i, encodedText));
}
}
最终测试的结果:
第0次编码的结果:$2a$06$ahAG0Hqch1kqCKueL8ecw.XFU8HHiO9k.HWuwXVd4iRiaVYd1Lko6
第1次编码的结果:$2a$06$IRmgGohEnNrC1xnhv0MomOly0AiIfBucBaTbx7QoZyj2lyilZCf9K
第2次编码的结果:$2a$06$dX.dZmRHMBC8RESaolKYU.u0cJmf70LRQbpnLvmZycOKPI4t34RJ2
扩展延伸
在某些应用场景BCrypt的就不大适用,如当系统需要采用Digest,HMac和AES对称加密等消息鉴别的场景,这类场景是需要共享一个相同的密钥的时候,Bcrypt因为相同原文输出不同的密钥,所以就无法共享相同的密钥。
Bcrypt编码算法应用在区块链技术上,很多人听说过比特币挖矿的原理是找sha256算法下计算出满足特定条件的nonce值,这种计算是要经过大量的运算,所以算力大的节点得到挖矿奖励的可能性越高,这点使得区块网络被算力大的控制,所以有些币考虑到这点,用Bcrypt算法替代sha256使得矿主无法通过投入算力来控制整个区块网络(因为Bcrypt算法慢),感兴趣的读者可以找相关资料。
最后基于上述建议在用户密码加密编码中放弃MD5吧,因为它即过时又缺乏安全性。