Spring Security 中BCryptPasswordEncoder加密和密码匹配原理

Spring Security 中BCryptPasswordEncoder加密和密码匹配原理
一、加密算法和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 = BCrypt.gensalt(strength, random);
    }
    else {
    salt = BCrypt.gensalt(strength);
    }
    }
    else {
    salt = BCrypt.gensalt();
    }
    return BCrypt.hashpw(rawPassword.toString(), 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);
     return rs.toString();
    

    }
    可以看到,这个方法中先根据传入的盐值salt,然后基于某种规则从salt得到real_salt,后续的操作都是用这个real_salt来进行,最终得到加密字符串。

所以这里有一个重点:传入的盐值salt并不是最终用来加密的盐,方法中通过salt得到了real_salt,记住这一点,因为后边的匹配方法matches中要用到这一点。

  1. 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.checkpw(rawPassword.toString(), 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用来加密,保证了这一点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值