springsecurity的BCryptPasswordEncoder加密和对密码验证的
原理BCryptPasswordEncoder加密和对密码验证的原理
⽬录
上⼀篇:
spring security中提供了⼀个加密类BCryptPasswordEncoder,可以⽤来对密码字符串进⾏加密,得到加密后的字符串。它采⽤哈希算法SHA-256 +随机盐+密钥对密码进⾏加密
⼀、加密算法和hash算法的区别
加密算法是⼀种可逆的算法,基本过程就是对原来为明⽂的⽂件或数据按某种算法进⾏处理,使其成为不可读的⼀段代码为“密⽂”,但在⽤相应的密钥进⾏操作之后就可以得到原来的内容。
哈希算法是⼀种不可逆的算法,是把任意长度的输⼊通过散列算法变换成固定长度的输出,输出就是散列值,不同的输⼊可能会散列成相同的输出,所以不可能从散列值来确定唯⼀的输⼊值。
⼆、源码解析
BCryptPasswordEncoder类实现了PasswordEncoder接⼝,这个接⼝中定义了两个⽅法
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}
其中encode(...)是对字符串进⾏加密的⽅法,matches使⽤来校验传⼊的明⽂密码rawPassword是否和加密密码encodedPassword相匹配的⽅法。即对密码进⾏加密时调⽤encode,登录认证时调⽤matches
下⾯我们来看下BCryptPasswordEncoder类中这两个⽅法的具体实现
1. encode⽅法
public String encode(CharSequence rawPassword) {
String salt;
if (strength > 0) {
if (random != null) {
salt = salt(strength, random);
}
else {
密码字符串是什么salt = salt(strength);
}
}
else {
salt = salt();
}
return BCrypt.String(), salt);
}
可以看到,这个⽅法中先基于某种规则得到了⼀个盐值,然后在调⽤BCrypt.hashpw⽅法,传⼊明⽂密码和盐值salt。所以我们再看下BCrypt.hashpw⽅法中做了什么
2. BCrypt.hashpw⽅法
public static String hashpw(String password, String salt) throws IllegalArgumentException {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char) 0;
int rounds, off = 0;
StringBuilder rs = new StringBuilder();
if (salt == null) {
throw new IllegalArgumentException("salt cannot be null");
}
int saltLength = salt.length();
if (saltLength < 28) {
throw new IllegalArgumentException("Invalid salt");
}
if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
throw new IllegalArgumentException("Invalid salt version");
}
if (salt.charAt(2) == '$') {
off = 3;
}
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$') {
throw new IllegalArgumentException("Invalid salt revision");
}
off = 4;
}
if (saltLength - off < 25) {
throw new IllegalArgumentException("Invalid salt");
}
/
/ Extract number of rounds
if (salt.charAt(off + 2) > '$') {
throw new IllegalArgumentException("Missing salt rounds");
}
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
}
catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds);
rs.append("$2");
if (minor >= 'a') {
rs.append(minor);
}
rs.append("$");
if (rounds < 10) {
rs.append("0");
}
rs.append(rounds);
rs.append("$");
encode_base64(saltb, saltb.length, rs);
encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);
String();
}
可以看到,这个⽅法中先根据传⼊的盐值salt,然后基于某种规则从salt得到real_salt,后续的操作都是⽤这个real_salt来进⾏,最终得到加密字符串。
所以这⾥有⼀个重点:传⼊的盐值salt并不是最终⽤来加密的盐,⽅法中通过salt得到了real_salt,记住这⼀点,因为后边的匹配⽅法matches中要⽤到这⼀点。
3. matches⽅法
matches⽅法⽤来判断⼀个明⽂是否和⼀个加密字符串对应。
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.String(), encodedPassword);
}
这个⽅法中先对密⽂字符串进⾏了⼀些校验,如果不符合规则直接返回不匹配,然后调⽤校验⽅法BCrypt.checkpw,第⼀个参数是明⽂,第⼆个参数是加密后的字符串。
public static boolean checkpw(String plaintext, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
}
static boolean equalsNoEarlyReturn(String a, String b) {
char[] caa = a.toCharArray();
char[] cab = b.toCharArray();
if (caa.length != cab.length) {
return false;
}
byte ret = 0;
for (int i = 0; i < caa.length; i++) {
ret |= caa[i] ^ cab[i];
}
return ret == 0;
}
注意equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed))这⾥,第⼀个参数是加密后的字符串,⽽第⼆个参数是⽤刚才提过的hashpw⽅法对明⽂字符串进⾏加密。
hashpw(plaintext, hashed)第⼀个参数是明⽂,第⼆个参数是加密字符串,但是在这⾥是作为盐值salt传⼊的,所以就⽤到了刚才说的 hashpw 内部通过传⼊的salt得到real_salt,这样就保证了对现在要校验的明⽂的加密和得到已有密⽂的加密⽤的是同样的加密策略,算法和盐值都相同,这样如果新产⽣的密⽂和原来的密⽂相同,则这两个密⽂对应的明⽂字符串就是相等的。
这也说明了加密时使⽤的盐值被写在了最终⽣成的加密字符串中。
三、总结
BCryptPasswordEncoder使⽤哈希算法+随机盐来对字符串加密。因为哈希是⼀种不可逆算法,所以密码认证时需要使⽤相同的算法+盐值来对待校验的明⽂进⾏加密,然后⽐较这两个密⽂来进⾏验证。BCryptPasswordEncoder在加密时通过从传⼊的salt中获取real_salt⽤来加密,保证了这⼀点。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。