摘要:在當今的數字世界中,密碼安全是至關重要的。為了保護使用者密碼免受未經授權的訪問和破解,Password-Based Key Derivation Function 2 (PBKDF2)演算法成為了一種重要的工具。
在 PBKDF2 演算法中,SHA 表示 Secure Hash Algorithm,它是一系列密碼雜湊函式的標準,其中 SHA-1、SHA-256、SHA-384 和 SHA-512 等是常見的版本。因此,PBKDF2-SHA 就是使用 SHA 系列演算法作為其內部雜湊函式的密碼派生函式。
PBKDF2-SHA 演算法的工作原理是透過多次迭代將輸入的密碼和鹽值進行混合和計算,以生成一個安全的金鑰。這樣設計可以增加攻擊者破解密碼所需的時間和資源成本,提高密碼的安全性。
PBKDF2演算法的優點
PBKDF2演算法具有以下優點:
2.1. 密碼安全性提升
PBKDF2演算法透過迭代應用一個偽隨機函式來增加密碼的安全性。這種迭代過程使得破解者需要更多的計算資源和時間來破解密碼,從而大大增加了密碼的安全性。
2.2. 強大的金鑰派生功能
PBKDF2演算法可以根據使用者提供的密碼和鹽值生成一個強大的金鑰。這個金鑰可以用於加密和解密資料,同時也可以用於生成訊息驗證碼等。
2.3. 可擴充套件性和靈活性
PBKDF2演算法可以根據需要進行迭代次數的調整,以適應不同的安全需求。這使得演算法具有較高的靈活性,並可以根據應用程式的要求進行調整。
3. PBKDF2演算法的缺點
儘管PBKDF2演算法具有許多優點,但也存在一些缺點:
3.1. 計算資源消耗較高
由於PBKDF2演算法需要進行多次迭代,因此它對計算資源的消耗相對較高。這可能會對一些資源有限的裝置或系統造成一定的負擔。
3.2. 不適合高速加密需求
由於PBKDF2演算法的計算量較大,它在高速加密需求的場景下可能表現不佳。對於這些場景,可以考慮使用更高效的金鑰派生函式。
4. PBKDF2演算法的應用
PBKDF2演算法主要應用於密碼儲存和驗證過程中。它解決了以下問題:
4.1. 密碼洩露的風險
透過將使用者密碼轉換為金鑰,PBKDF2演算法可以大大降低密碼洩露的風險。即使攻擊者獲取了儲存的密碼資料,他們也無法輕易地破解出原始密碼。
4.2. 弱密碼的安全性
PBKDF2演算法可以增加弱密碼的安全性。透過迭代和鹽值的引入,即使使用者選擇了弱密碼,破解者也需要付出更大的代價才能破解密碼。
在keycloak中的應用
在使用 PBKDF2-SHA256 演算法進行密碼雜湊時,通常會將生成的鹽值和雜湊後的密碼一起儲存在資料庫中。當使用者下次輸入相同的明文密碼時,您需要按照以下步驟來對比使用者輸入的密碼與庫裡儲存的密碼是否相同:
- 從資料庫中獲取該使用者的鹽值和已經雜湊後的密碼。
- 使用獲取到的鹽值和使用者輸入的明文密碼,結合 PBKDF2-SHA256 演算法再次計算雜湊值。
- 將上一步得到的雜湊值與資料庫中儲存的雜湊密碼進行比較。
- 如果兩個雜湊值相同,則說明使用者輸入的密碼是正確的;如果不同,則密碼不匹配。
簡而言之,您需要在使用者登入時重新計算雜湊值,並將其與資料庫中儲存的雜湊密碼進行比較以驗證使用者身份。這樣設計可以保證使用者密碼的安全性,同時也能夠防止彩虹表攻擊等惡意破解手段。
PBKDF2工具類
/**
* PB KDF2 SHA工具類
*
* @author lind
* @date 2024/5/8 8:20
* @since 1.0.0
*/
public class PBKDF2SHAUtils {
private static final String PBKDF_2_WITH_HMAC_SHA_512 = "PBKDF2WithHmacSHA512";
private static final int ITERATIONS = 30000;
private static final int DERIVED_KEY_SIZE = 256;
/**
* PB KDF2 SHA256加密
* @param rawPassword
* @param salt
* @return
*/
public static String encodedCredential(String rawPassword,byte[] salt){
return encodedCredential(rawPassword, ITERATIONS,salt, DERIVED_KEY_SIZE);
}
// 加密
public static String encodedCredential(String rawPassword, int iterations, byte[] salt, int derivedKeySize) {
KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, derivedKeySize);
try {
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
return new String(Base64.getEncoder().encode(key));
}
catch (InvalidKeySpecException e) {
throw new RuntimeException("Credential could not be encoded", e);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
// 隨機鹽
public static byte[] getSalt() {
byte[] buffer = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(buffer);
return buffer;
}
private static SecretKeyFactory getSecretKeyFactory() {
try {
return SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_512);
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException("PBKDF2 algorithm not found", e);
}
}
}
單元測試程式碼
@Test
public void generatePassword() {
String rawPassword = "123456";//原密碼
byte[] salt = PBKDF2SHAUtils.getSalt();// 隨機鹽,需要儲存
String encodePass = PBKDF2SHAUtils.encodedCredential(rawPassword, salt);// 秘文,需要儲存
System.out.println("encodePass:" + encodePass);
String formData = "123456"; // 表單資料
Assert.equals(encodePass, PBKDF2SHAUtils.encodedCredential(formData, salt));// 與庫裡密碼對比
}