2026/05/25

PBKDF2

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

References

PBKDF2 - 維基百科,自由的百科全書

沒有留言:

張貼留言