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

沒有留言:

張貼留言