您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Spring security BCryptPasswordEncoder密碼驗(yàn)證原理的示例分析,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
一、加密算法和hash算法的區(qū)別
加密算法是一種可逆的算法,基本過(guò)程就是對(duì)原來(lái)為明文的文件或數(shù)據(jù)按某種算法進(jìn)行處理,使其成為不可讀的一段代碼為“密文”,但在用相應(yīng)的密鑰進(jìn)行操作之后就可以得到原來(lái)的內(nèi)容 。
哈希算法是一種不可逆的算法,是把任意長(zhǎng)度的輸入通過(guò)散列算法變換成固定長(zhǎng)度的輸出,輸出就是散列值,不同的輸入可能會(huì)散列成相同的輸出,所以不可能從散列值來(lái)確定唯一的輸入值。
二、源碼解析
BCryptPasswordEncoder類(lèi)實(shí)現(xiàn)了PasswordEncoder接口,這個(gè)接口中定義了兩個(gè)方法
public interface PasswordEncoder { String encode(CharSequence rawPassword); boolean matches(CharSequence rawPassword, String encodedPassword); }
其中encode(...)是對(duì)字符串進(jìn)行加密的方法,matches使用來(lái)校驗(yàn)傳入的明文密碼rawPassword是否和加密密碼encodedPassword相匹配的方法。即對(duì)密碼進(jìn)行加密時(shí)調(diào)用encode,登錄認(rèn)證時(shí)調(diào)用matches
下面我們來(lái)看下BCryptPasswordEncoder類(lèi)中這兩個(gè)方法的具體實(shí)現(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); }
可以看到,這個(gè)方法中先基于某種規(guī)則得到了一個(gè)鹽值,然后在調(diào)用BCrypt.hashpw方法,傳入明文密碼和鹽值salt。所以我們?cè)倏聪翨Crypt.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(); }
可以看到,這個(gè)方法中先根據(jù)傳入的鹽值salt,然后基于某種規(guī)則從salt得到real_salt,后續(xù)的操作都是用這個(gè)real_salt來(lái)進(jìn)行,最終得到加密字符串。
所以這里有一個(gè)重點(diǎn):傳入的鹽值salt并不是最終用來(lái)加密的鹽,方法中通過(guò)salt得到了real_salt,記住這一點(diǎn),因?yàn)楹筮叺钠ヅ浞椒╩atches中要用到這一點(diǎn)。
3. matches方法
matches方法用來(lái)判斷一個(gè)明文是否和一個(gè)加密字符串對(duì)應(yīng)。
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); }
這個(gè)方法中先對(duì)密文字符串進(jìn)行了一些校驗(yàn),如果不符合規(guī)則直接返回不匹配,然后調(diào)用校驗(yàn)方法BCrypt.checkpw,第一個(gè)參數(shù)是明文,第二個(gè)參數(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))這里,第一個(gè)參數(shù)是加密后的字符串,而第二個(gè)參數(shù)是用剛才提過(guò)的hashpw方法對(duì)明文字符串進(jìn)行加密。
hashpw(plaintext, hashed)第一個(gè)參數(shù)是明文,第二個(gè)參數(shù)是加密字符串,但是在這里是作為鹽值salt傳入的,所以就用到了剛才說(shuō)的 hashpw 內(nèi)部通過(guò)傳入的salt得到real_salt,這樣就保證了對(duì)現(xiàn)在要校驗(yàn)的明文的加密和得到已有密文的加密用的是同樣的加密策略,算法和鹽值都相同,這樣如果新產(chǎn)生的密文和原來(lái)的密文相同,則這兩個(gè)密文對(duì)應(yīng)的明文字符串就是相等的。
這也說(shuō)明了加密時(shí)使用的鹽值被寫(xiě)在了最終生成的加密字符串中。
關(guān)于“Spring security BCryptPasswordEncoder密碼驗(yàn)證原理的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。