PBKDF1 和 PBKDF2 是 Password-Based Key Derivation Function 1 and 2,具有可改變計算成本的金鑰衍生函式。PBKDF 是 RSA 的 PKCS 的一部分。PBKDF1 只能產生 160 bits 長度的金鑰。2000 年發布了 PBKDF2,也就是 PKCS #5 v2.0,以 RFC 2898 發布。2017 年發布了 PBKDF2 的更新版本,也就是 RFC 8018,PKCS #5 v2.1 版。
PBKDF2 將 HMAC (hash-based message authentication code) 與 salt 一起套用於密碼加密,並多次重複這個過程,產生一個 derived key,增加了 CPU 運算,讓破解變得困難。
2000 年的演算法建議最小疊代次數為 1000 次,隨著 CPU 效能提升,2005 年 Kerberos 標準建議改為 4096 次。apple 在 ios3 使用 3000 次,在 ios4 為 10000 次。2023 年,OWASP 建議PBKDF2-HMAC-SHA256使用600,000次迭代,PBKDF2-HMAC-SHA512使用210,000 次迭代。
PBKDF2 雖然可以通過改變迭代次數來任意調整所需的計算時間,但它可以用一個小電路和很少的RAM來實現,透過使用特殊應用積體電路(ASIC)或圖形處理器(GPU)進行暴力攻擊會比較容易。
Bcrypt 密碼雜湊函式需要更大的RAM(但仍然不能單獨調整,由給定的CPU時間決定)並且對此類攻擊的抵抗力稍強,更現代的Scrypt金鑰衍生函式可以任意使用大量主記憶體,因此更能抵抗ASIC和GPU攻擊。
2013 年舉辦的 Password Hashing Competition (PHC) 鼓勵開發更好的密碼 hash 演算法。於 2015/7/20,Argon2 被選為最終的獲勝者。
java sample
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Base64;
public class PBKDF2Example {
private static final int SALT_LENGTH = 16; // 16 bytes
private static final int ITERATIONS = 65536; // iteration count
private static final int KEY_LENGTH = 256; // 256-bit
// 產生隨機 salt
public static byte[] generateSalt() {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
sr.nextBytes(salt);
return salt;
}
// 將密碼 + salt 轉為 PBKDF2 hash
public static byte[] hashPassword(char[] password, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return skf.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Error while hashing password", e);
}
}
// 驗證密碼
public static boolean verifyPassword(char[] password, byte[] salt, byte[] expectedHash) {
byte[] pwdHash = hashPassword(password, salt);
if (pwdHash.length != expectedHash.length) return false;
for (int i = 0; i < pwdHash.length; i++) {
if (pwdHash[i] != expectedHash[i]) return false;
}
return true;
}
public static void main(String[] args) {
String password = "MyPassword123!";
// 生成 salt
byte[] salt = generateSalt();
// 生成 hash
byte[] hash = hashPassword(password.toCharArray(), salt);
System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt));
System.out.println("PBKDF2 Hash: " + Base64.getEncoder().encodeToString(hash));
// 驗證正確密碼
boolean valid = verifyPassword(password.toCharArray(), salt, hash);
System.out.println("Password matched? " + valid);
// 驗證錯誤密碼
boolean invalid = verifyPassword("wrongPassword".toCharArray(), salt, hash);
System.out.println("Wrong password verified? " + invalid);
}
}
執行結果
Salt: aDRyAM6YJD1p8W0SN8U7pg==
PBKDF2 Hash: 2lXNtraA2JIUdSNL5xcj/seHjOpIuTxkarTxKItRabw=
Password matched? true
Wrong password verified? false