首先明确一点,那就是 BCrypt 算法是一种 单向Hash加密算法。
算法 | 特点 | 有效破解方式 | 破解难度 | 其它 |
---|---|---|---|---|
对称加密 | 可以解密出明文 | 获取密钥 | 中 | 需要确保密钥不被泄露 |
单向Hash | 不可解密 | 碰撞/彩虹表 | 中 | 可以通过加盐和多次hash来提高安全性,确保盐不被泄漏 |
Pbkdf2 | 不可解密 | 暂无 | 难 | 需要设定合理的参数 |
加密过程
直接上代码:以 spring-security-core-5.3.4.RELEASE-sources.jar
包中为例,在 BCryptPasswordEncoder.java
中,可以清晰的看到,有多个构造参数,构造参数重,有3个主要的参数:
-
BCryptVersion
加密算法的版本,默认 $2A,除此之外可选的有$2Y和$2B; -
strength
个人理解的是单向hash的次数,默认值是10,可选4打31位; -
random
安全的随机数生成器,不指定的话,就是 BCrypt.gensalt(version.getVersion(), strength) 方法来生成
上面这些都可以不用指定,有默认值。加密调用的方法是org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#encode
。具体源码如下:
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt;
if (random != null) {
salt = BCrypt.gensalt(version.getVersion(), strength, random);
} else {
salt = BCrypt.gensalt(version.getVersion(), strength);
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}
其中可以看出,关键的步骤在 BCrypt.hashpw(rawPassword.toString(), salt);
中。感兴趣具体的实现,可以再去对照源码进行分析。笔者更在意它生成后的存储格式,加密后的秘文格式如下:
通常,这部分格式也是最终代码入库的部分。
解密过程
解密的思路,从已经加密后的秘文中,取出盐值,然后同用户输入的明文,进行BCrypt算法加密。将加密后的数据同数据库中存储的数据进行比较,如果相同,则认为密码输入正确,否则校验失败。具体的org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#matches
源码如下:
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
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;
}
// 将明文同已经经过加盐+BCrypt算法加密后秘文进行比较
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
/**
* Check that a password (as a byte array) matches a previously hashed
* one
* @param passwordb the password to verify, as a byte array
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
* @since 5.3
*/
public static boolean checkpw(byte[] passwordb, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
}
个人感觉讲述的比较清晰资料
参考资料: