SpringSecurity中的Bcrypt加密方法源碼解析

spring Security中的BCryptPasswordEncoder類采用SHA-256 +隨機鹽+密鑰對密碼進行加密。SHA是一系列的加密算法,有SHA-1、SHA-2、SHA-3三大類,SHA-256是SHA-2下細分出的一種算法,此算法發生哈希碰撞的概率幾乎為0,安全性高。

  • BCryptPasswordEncoder類實現了PasswordEncoder接口的encode和matches方法,來進行密碼加密和匹配
    1. 加密(encode):注冊用戶時,使用SHA-256+隨機鹽+密鑰把用戶輸入的密碼進行hash處理,得到密碼的hash值,然后將其存入數據庫中。
      • BCryptPasswordEncoder類定義了兩個final變量,用來控制encode方法的加密規則。strength是一個取值在-1或者4~31之間的int變量,而繼承了java.util.random的SecureRandom類則提供了一種強加密RNG手段(PRNG),random是一個SecureRandom類的final變量,為后續生成salt起作用。
        private final int strength;
        private final SecureRandom random;
      
      • encode方法根據strength值的不同和有無SecureRandom對象使用了三種方式生成salt,但這三種方式本質其實是類似的,底層都是調用BCrypt類的gensalt(this.strength, this.random)方法,只是如果沒有傳入自定義的strength和SecureRandom對象,BCrypt類會自動幫我們將strength設為10和實例化SecureRandom對象傳入方法中:
      public String encode(CharSequence rawPassword) {
      //聲明一個“鹽”變量
      String salt;
      //生成隨機鹽
      if (this.strength > 0) {
          if (this.random != null) {
              salt = BCrypt.gensalt(this.strength, this.random);
          } else {
              salt = BCrypt.gensalt(this.strength);
          }
            } else {
          salt = BCrypt.gensalt();
                }
            return BCrypt.hashpw(rawPassword.toString(), salt);
          }
      
      • 只有當strength在[4,31]取值時,gensalt方法才會返回“鹽”值,此方法通過調用random.nextBytes()和encode_base64()方法編碼生成隨機鹽字符串;nextBytes()方法會調用SecureRandomSpi抽象類的engineNextBytes方法生成一串長度為16隨機的byte數組,而encode_base64()方法通過多次借助byte數組和長度為64的char數組base64_code(包含大部分ASCII字符)進行Base64編碼,最終生成長度為29的隨機鹽salt字符串。
      public static String gensalt(int log_rounds, SecureRandom random) {
         if (log_rounds >= 4 && log_rounds <= 31) {
         StringBuilder rs = new StringBuilder();
         byte[] rnd = new byte[16];
         random.nextBytes(rnd);
         rs.append("$2a$");
         if (log_rounds < 10) {
             rs.append("0");
         }
         rs.append(log_rounds);
         rs.append("$");
         encode_base64(rnd, rnd.length, rs);
         return rs.toString();
      } else {
         throw new IllegalArgumentException("Bad number of rounds");
      }
      }
      
      static void encode_base64(byte[] d, int len, StringBuilder rs) throws IllegalArgumentException {
      int off = 0;
      if (len > 0 && len <= d.length) {
         while(off < len) {
             int c1 = d[off++] & 255;
             rs.append(base64_code[c1 >> 2 & 63]);
             c1 = (c1 & 3) << 4;
             if (off >= len) {
                 rs.append(base64_code[c1 & 63]);
                 break;
             }
             int c2 = d[off++] & 255;
             c1 |= c2 >> 4 & 15;
             rs.append(base64_code[c1 & 63]);
             c1 = (c2 & 15) << 2;
             if (off >= len) {
                 rs.append(base64_code[c1 & 63]);
                 break;
             }
             c2 = d[off++] & 255;
             c1 |= c2 >> 6 & 3;
             rs.append(base64_code[c1 & 63]);
             rs.append(base64_code[c2 & 63]);
             }
         } else {
         throw new IllegalArgumentException("Invalid len");
         }
      }
      
      • 將生成的鹽值和原始密碼傳入BCrypt類的hashpw()方法進行加密,該方法對傳入的鹽進行了一系列校驗(長度、版本等等),確保是有效salt。同時將原始密碼轉成passwordb字節數組。一個有效的salt前7位是校驗位,包含了鹽版本、鹽rounds,第8位到30位為real_salt,將real_salt傳入decode_base64方法進行轉碼,字符串real_salt被轉換成字節數組輸出給長度為16的saltb,接著將saltb和passwordb字節數組傳入crypt_raw()方法進行SHA-256加密生成偽隨機hash值,最后將saltb和hash值分別進行encode_base64方法進行Base64編碼(其中saltb字節數組通過編碼重新變成realSalt字符串,這也是后續matches方法匹配密碼的\color{red}{關鍵點}),產生的結果拼接成60位的隨機密碼,前7位同樣是校驗位,第8位到30位為real_salt。
      public static String hashpw(String password, String salt) throws IllegalArgumentException {
                  .....dosomework.....
          int rounds = Integer.parseInt(salt.substring(off, off + 2));
           String real_salt = salt.substring(off + 3, off + 25);
           byte[] passwordb;
           try {
            passwordb = (password + (minor >= 'a' ? "\u0000" : "")).getBytes("UTF-8");
        } catch (UnsupportedEncodingException var13) {
              throw new AssertionError("UTF-8 is not supported");
           }
            byte[] saltb = decode_base64(real_salt, 16);
            BCrypt B = new BCrypt();
            byte[] 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();    
        }
      
    2. 密碼匹配(matches):用戶登錄時,密碼匹配階段并沒有進行密碼解密(因為密碼經過Hash處理,是不可逆的),而是將輸入的密碼與數據庫查出的密碼同樣傳入BCrypt類的pwhash()中進行加密,由于算法將加密后密碼的第8位到30位作為real_salt,第一次執行pwhash方法傳入的鹽和第二次傳入的鹽值(數據庫密碼)是包含關系,兩者的前30位是相同的。那么根據相同的real_salt和相同的password生成的加密密碼很顯然也是相同的。
       public boolean matches(CharSequence rawPassword, String encodedPassword) {
            if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
              this.logger.warn("Encoded password does not look like BCrypt");
              return false;
          } else {
              return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
              }
          } else {
              this.logger.warn("Empty encoded password");
              return false;
          }
      }
      
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 這篇文章主要講述在Mobile BI(移動商務智能)開發過程中,在網絡通信、數據存儲、登錄驗證這幾個方面涉及的加密...
    雨_樹閱讀 2,654評論 0 6
  • 在開發應用過程中,客戶端與服務端經常需要進行數據傳輸,涉及到重要隱私安全信息時,開發者自然會想到對其進行加密,即使...
    閑庭閱讀 3,304評論 0 11
  • 首先羅列一些知識點: 1.加密算法通常分為對稱性加密算法和非對稱性加密算法:對于對稱性加密算法,信息接收雙方都需事...
    神木驚蟄閱讀 370評論 0 0
  • 命運好像在暗中播下了不幸的種子,后來的人都可能會是悲劇的延續。 ①遺棄 媽媽出生在七十年代,那個時候沒有要求...
    涵瑜姑娘閱讀 743評論 0 5
  • 早年間,美國有家電視臺,播出了一個很轟動的節目。這個節目名稱叫“透視你的心”看似很奇異,就是每一個上臺的測試者,都...
    九零邦閱讀 638評論 1 0