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 - 維基百科,自由的百科全書

2026/05/18

bcrypt

bcrypt 是一種專為密碼儲存設計的單向雜湊演算法(hashing algorithm)。 由美國電腦科學家 Niels Provos 及 David Mazières 根據 Blowfish 加密演算法所設計的密碼雜湊函式,於1999年在USENIX中展示。並加入了:

  • Salt — 防止字典攻擊與 rainbow table 攻擊

  • 工作因子(cost factor) — 運算次數 = 2^cost,控制計算複雜度,讓暴力破解更慢

Cost 大約耗時(現代CPU) 用途建議
8 約 50ms 測試環境
10 約 100–200ms 一般 Web 登入
12 約 300–500ms 高安全性需求
14+ >1 秒 金融級或低頻操作

bcrypt 運作流程

注意密碼最多只支援 72 bytes

  1. 輸入:
  • 明文密碼(例如 "MyPassword123!"),最多 72 bytes

  • 工作因子(cost,例如 10)

  1. 產生隨機 Salt(16 bytes), 是一個 16 bytes(128 bits) salt value

  2. 進行多輪 Blowfish 加密運算,運算次數 = 2^cost ex: cost=10 → 1024 次加密循環 cost=12 → 4096 次循環

  3. 輸出:產生一個 60 字元的 hash 字串,裡面包含版本, cost factor, salt及 hash 結果

bcrypt 格式

#格式
$2<a/b/x/y>$[cost]$[22 character salt][31 character hash]
# sample
$2a$10$E6h8dSmDsC8Kz.bJb3SuPO0h9msLniD9pD8bZjq4nLZdTIm.CDzMy
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW

格式說明

$2a$    → bcrypt 版本  
10$     → cost factor  
E6h8dSmDsC8Kz.bJb3SuPO  → Base64 編碼的 salt  
0h9msLniD9pD8bZjq4nLZdTIm.CDzMy  → hash 結果
$2a$    → bcrypt 版本  
12$     → cost factor  
R9h/cIPz0gi.URNNX3kh2O  → Base64 編碼的 salt  
PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW  → hash 結果

Version

$2$ (1999)

最初始的 bcrypt 版本

在 OpenBSD 密碼檔案裡面用以下幾種方式定義使用哪一種加密方式

  • $1$: MD5-based crypt ('md5crypt')
  • $2$: Blowfish-based crypt ('bcrypt')
  • $sha1$: SHA-1-based crypt ('sha1crypt')
  • $5$: SHA-256-based crypt ('sha256crypt')
  • $6$: SHA-512-based crypt ('sha512crypt')

$2a$

初始版本的 bcrypt,沒有定義如何處理 non-ASCII 字元,也沒有處理 null terminator。這個版本的 bcrypt 限制

  • 字串必須要用UTF-8 encoding

  • 必須要包含 null terminator

$2x$, $2y$ (June 2011)

因 2011/6 在 php 版本的 crypt_blowfish 發現了一個 bug,在 8th bit 設定時,會處理錯誤。故建議把 $2a$更新為 $2x$ ,同時做了 $2y$。但 2x/2y 都沒有被廣泛採用

$2b$ (February 2014)

OpenBSD 版本的 bcrypt 發現了一個 bug,是使用 unsigned 8-bit 數值儲存密碼長度。超過 255 bytes 的密碼,會以少於 72 bytes 的長度切割。 ex: 260 bytes 被以 4 bytes 切割。

OpenBSD 修正這個問題,並改版為 $2b$

Java Sample

有兩個 java library,org.mindrot.jbcrypt 版本比較舊,沒有支援新版的演算法,且已經沒有在維護。at.favre.lib.crypto.bcrypt 有支援新的演算法,且有支援超過 72 bytes 密碼的處理方式。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.maxkit.test</groupId>
    <artifactId>test</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>0.4</version>
        </dependency>

        <dependency>
            <groupId>at.favre.lib</groupId>
            <artifactId>bcrypt</artifactId>
            <version>0.10.2</version>
        </dependency>
    </dependencies>
</project>

MindrotBcrypt.java

import org.mindrot.jbcrypt.BCrypt;

public class MindrotBcrypt {
    public static void main(String[] args) {
        String password = "MyPassword123!";

        // cost factor (越大越慢,預設常見是 10~12)
        int cost = 12;

        // 產生 salt
        String salt = BCrypt.gensalt(cost);
        System.out.println("Salt: " + salt);

        // Hash 密碼
        String hashed = BCrypt.hashpw(password, salt);
        System.out.println("BCrypt Hash: " + hashed);

        // 驗證密碼
        boolean matched = BCrypt.checkpw(password, hashed);
        System.out.println("Password matched? " + matched);

        // 測試錯誤密碼
        boolean matchedWrong = BCrypt.checkpw("WrongPassword", hashed);
        System.out.println("Wrong password verified? " + matchedWrong);
    }
}

執行結果

Salt: $2a$12$.ZzlOaYpDmUSvFCq/FJaM.
BCrypt Hash: $2a$12$.ZzlOaYpDmUSvFCq/FJaM.lEUlNjTrXH8JuUAN96fq80yiKSyV0Q.
Password matched? true
Wrong password verified? false

BcryptFavreBcrypt.java

import at.favre.lib.crypto.bcrypt.BCrypt;

public class BcryptFavreBcrypt {
    public static void main(String[] args) {
        String password = "MyPassword123!";
        int cost = 12;

        // 產生 hash
        String bcryptHashString = BCrypt.withDefaults().hashToString(cost, password.toCharArray());
        System.out.println("Hash: " + bcryptHashString);

        // 驗證密碼
        BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), bcryptHashString);
        if (result.verified) {
            System.out.println("Password verified OK");
        } else {
            System.out.println("Password verification failed");
        }

        // 錯誤密碼測試
        BCrypt.Result resultWrong = BCrypt.verifyer().verify("WrongPassword".toCharArray(), bcryptHashString);
        System.out.println("Wrong password verified? " + resultWrong.verified);

        //////
        // 產生 2y hash
        String bcryptHashString2Y = BCrypt.with(BCrypt.Version.VERSION_2Y).hashToString(cost, password.toCharArray());
        System.out.println("2Y Hash: " + bcryptHashString2Y);
        // 驗證密碼
        BCrypt.Result result2Y = BCrypt.verifyer().verify(password.toCharArray(), bcryptHashString2Y);
        if (result2Y.verified) {
            System.out.println("2Y Password verified OK");
        } else {
            System.out.println("2Y Password verification failed");
        }

        // 產生 2b hash
        String bcryptHashString2B = BCrypt.with(BCrypt.Version.VERSION_2Y).hashToString(cost, password.toCharArray());
        System.out.println("2B Hash: " + bcryptHashString2B);
        // 驗證密碼
        BCrypt.Result result2B = BCrypt.verifyer().verify(password.toCharArray(), bcryptHashString2B);
        if (result2B.verified) {
            System.out.println("2B Password verified OK");
        } else {
            System.out.println("2B Password verification failed");
        }
    }
}

執行結果

Hash: $2a$12$BUc45rHJsnLWDkFW8CJN4.5QB.KUSg2ofIcejoeJ24OTmznz8BD8a
Password verified OK
Wrong password verified? false
2Y Hash: $2y$12$vRH74gxo2LZN/wbFj4oKdO6RrGgOgWTHxrhw.OUoMt9ZT1WqPT8Uq
2Y Password verified OK
2B Hash: $2y$12$7y.j7ocPnDqcbspURCddzeTn25HWgtQR1LHy20Wo./N81ygBd7m92
2B Password verified OK

References

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

2026/05/04

Java Atomic Variables

java.util.concurrent.atomic 定義了對單一變數進行 atomic operations 的類別,所有類別都有 get 與 set methods,可讀寫 volatile variables。set 可確定會在 get 的前面先處理。

對於物件管理,如果有兩個 thread 同時做 get/set,會導致資料異常,所以通常會在 set method 加上 synchronized 來限制一次只有一個 thread 進入該 method

例如

Counter 類別的變數 c,會因為多個 thread 同時使用而異常

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

所以會改寫為

class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }

}

synchronized 可解決 multi-thread 問題,但會產生效能問題。

以 AtomicInteger 改寫

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
    private AtomicInteger c = new AtomicInteger(0);

    public void increment() {
        c.incrementAndGet();
    }

    public void decrement() {
        c.decrementAndGet();
    }

    public int value() {
        return c.get();
    }

}

Atomic Operation

non-blocking 演算法提供了 compare-and-swap CAS 方法,可確保資料完整性。

CAS operation 使用以下三個 operands

  1. M 要操作 operate 的記憶體位置

  2. A 該變數期待的既有原始數值

  3. B 需要被設定的新數值

CAS operation 可自動 atomically 從 M 到 B 更新數值,只會在 M 確認為 A 時,更新為 B

因此可不使用 synchronization lock,完成資料的更新,沒有 thread 會被 suspended,也不需要 context switching。


常用的 Atomic Variables 有 AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference,分別代表 int, long, boolean, object reference 這些數值可自動被安全地更新,主要 methods 有

  • get()

    等同於讀取 volatile variable,可直接取得其他 thread 更新的數值

  • incrementAndGet()

  • set()

    等同於寫入 volatile variable

  • lazySet()

    最終會寫入變數,但不保證可立即被其他 thread 取得。速度比 set 更快。可用在 cache,或被 gc 的資料

  • compareAndSet()

    成功就回傳 true

  • weakCompareAndSetPlain(), weakCompareAndSetVolatile()

    weakCompareAndSet() 已被 deprecated

References

# Atomic Variables Java Tutorial

An Introduction to Atomic Variables in Java | Baeldung

java.util.concurrent.atomic (Java SE 25 & JDK 25)

Atomic Variables in Java with Examples - GeeksforGeeks