Spring Security 中的 BCryptPasswordEncoder加密、驗證策略

獨家雨天發表於2020-11-27

首先明確一點,那就是 BCrypt 演算法是一種 單向Hash加密演算法

演算法特點有效破解方式破解難度其它
對稱加密可以解密出明文獲取金鑰需要確保金鑰不被洩露
單向Hash不可解密碰撞/彩虹表可以通過加鹽和多次hash來提高安全性,確保鹽不被洩漏
Pbkdf2不可解密暫無需要設定合理的引數

加密過程

直接上程式碼:以 spring-security-core-5.3.4.RELEASE-sources.jar 包中為例,在 BCryptPasswordEncoder.java 中,可以清晰的看到,有多個構造引數,構造引數重,有3個主要的引數:

  1. BCryptVersion 加密演算法的版本,預設 $2A,除此之外可選的有$2Y和$2B;
  2. strength 個人理解的是單向hash的次數,預設值是10,可選4打31位;
  3. random安全的隨機數生成器,不指定的話,就是 BCrypt.gensalt(version.getVersion(), strength) 方法來生成

上面這些都可以不用指定,有預設值。加密呼叫的方法是org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#encode 。具體原始碼如下:

	public String encode(CharSequence rawPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}

		String salt;
		if (random != null) {
			salt = BCrypt.gensalt(version.getVersion(), strength, random);
		} else {
			salt = BCrypt.gensalt(version.getVersion(), strength);
		}
		return BCrypt.hashpw(rawPassword.toString(), salt);
	}

其中可以看出,關鍵的步驟在 BCrypt.hashpw(rawPassword.toString(), salt); 中。感興趣具體的實現,可以再去對照原始碼進行分析。筆者更在意它生成後的儲存格式,加密後的祕文格式如下:

img

通常,這部分格式也是最終程式碼入庫的部分。

解密過程

解密的思路,從已經加密後的祕文中,取出鹽值,然後同使用者輸入的明文,進行BCrypt演算法加密。將加密後的資料同資料庫中儲存的資料進行比較,如果相同,則認為密碼輸入正確,否則校驗失敗。具體的org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#matches原始碼如下:

	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}

		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;
		}
		// 將明文同已經經過加鹽+BCrypt演算法加密後祕文進行比較
		return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
	}

	/**
	 * Check that a password (as a byte array) matches a previously hashed
	 * one
	 * @param passwordb	the password to verify, as a byte array
	 * @param hashed	the previously-hashed password
	 * @return	true if the passwords match, false otherwise
	 * @since 5.3
	 */
	public static boolean checkpw(byte[] passwordb, String hashed) {
		return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
	}

個人感覺講述的比較清晰資料

參考資料:

  1. Bcrypt加密之新認識 - 簡書
  2. BCryptPasswordEncoder加密、驗證策略_kevin-CSDN部落格_bcryptpasswordencoder

相關文章