Spring security BCryptPasswordEncoder密碼驗證原理詳解
一、加密算法和hash算法的區(qū)別
加密算法是一種可逆的算法,基本過程就是對原來為明文的文件或數(shù)據(jù)按某種算法進行處理,使其成為不可讀的一段代碼為“密文”,但在用相應的密鑰進行操作之后就可以得到原來的內(nèi)容 。
哈希算法是一種不可逆的算法,是把任意長度的輸入通過散列算法變換成固定長度的輸出,輸出就是散列值,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。
二、源碼解析
BCryptPasswordEncoder類實現(xiàn)了PasswordEncoder接口,這個接口中定義了兩個方法
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}
其中encode(...)是對字符串進行加密的方法,matches使用來校驗傳入的明文密碼rawPassword是否和加密密碼encodedPassword相匹配的方法。即對密碼進行加密時調(diào)用encode,登錄認證時調(diào)用matches
下面我們來看下BCryptPasswordEncoder類中這兩個方法的具體實現(xiàn)
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);
}
可以看到,這個方法中先基于某種規(guī)則得到了一個鹽值,然后在調(diào)用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();
}
可以看到,這個方法中先根據(jù)傳入的鹽值salt,然后基于某種規(guī)則從salt得到real_salt,后續(xù)的操作都是用這個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.checkpw(rawPassword.toString(), encodedPassword);
}
這個方法中先對密文字符串進行了一些校驗,如果不符合規(guī)則直接返回不匹配,然后調(diào)用校驗方法BCrypt.checkpw,第一個參數(shù)是明文,第二個參數(shù)是加密后的字符串。
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))這里,第一個參數(shù)是加密后的字符串,而第二個參數(shù)是用剛才提過的hashpw方法對明文字符串進行加密。
hashpw(plaintext, hashed)第一個參數(shù)是明文,第二個參數(shù)是加密字符串,但是在這里是作為鹽值salt傳入的,所以就用到了剛才說的 hashpw 內(nèi)部通過傳入的salt得到real_salt,這樣就保證了對現(xiàn)在要校驗的明文的加密和得到已有密文的加密用的是同樣的加密策略,算法和鹽值都相同,這樣如果新產(chǎn)生的密文和原來的密文相同,則這兩個密文對應的明文字符串就是相等的。
這也說明了加密時使用的鹽值被寫在了最終生成的加密字符串中。
三、總結(jié)
BCryptPasswordEncoder使用哈希算法+隨機鹽來對字符串加密。因為哈希是一種不可逆算法,所以密碼認證時需要使用相同的算法+鹽值來對待校驗的明文進行加密,然后比較這兩個密文來進行驗證。BCryptPasswordEncoder在加密時通過從傳入的salt中獲取real_salt用來加密,保證了這一點。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于MyBatis模糊查詢的幾種實現(xiàn)方式
在實際項目中,我們會經(jīng)常對數(shù)據(jù)做一些模糊查詢的操作,這時候就需要利用到 like字段,那么在Mybatis中,有哪些方式可以實現(xiàn)模糊查詢呢,需要的朋友可以參考下2023-05-05
windows系統(tǒng)使用mvn命令打包并指定jdk路徑方式
這篇文章主要介紹了windows系統(tǒng)使用mvn命令打包并指定jdk路徑方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
SpringSecurity微服務(wù)實戰(zhàn)之公共模塊詳解
這篇文章主要為大家介紹了SpringSecurity微服務(wù)實戰(zhàn)之公共模塊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
解析springboot整合谷歌開源緩存框架Guava Cache原理
本文主要為大家解析了springboot整合谷歌開源緩存框架Guava Cache的原理以及在實際開發(fā)過程中的使用,附含源碼,有需要的朋友可以參考下2021-08-08

