溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring security BCryptPasswordEncoder密碼驗(yàn)證原理的示例分析

發(fā)布時(shí)間:2021-06-08 11:47:26 來(lái)源:億速云 閱讀:181 作者:小新 欄目:編程語(yǔ)言

這篇文章將為大家詳細(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)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI