---
# System prepended metadata

title: Java 中的 Hashing
tags: [Security, UUID, Salt, Hashing, PBKDF2, Hash, SecureRandom, MessageDigest, Encryption, Mac, Java]

---

# Java 中的 Hashing

## 1. 什麼是 Hashing？

### **定義與概念**
Hashing 是一種將輸入數據透過特定的演算法轉換為固定長度的值（通常稱為 Hash 值或雜湊值）的技術。這些 Hash 值用於快速查找、驗證數據完整性，或作為唯一標識符。

### **為何需要 Hashing？**
Hashing 在計算機科學中被廣泛應用，主要有以下幾個原因：
- **高效搜尋**：透過 Hashing，可以快速存取資料，例如在 `HashMap` 或 `HashSet` 中進行 O(1) 時間複雜度的查找。

> **O(1) 時間複雜度（常數時間）**：表示操作的執行時間與輸入大小無關，無論數據有多少，每次查找或插入的時間幾乎不變。例如，在 HashMap 中，透過 Hashing 直接定位數據位置，因此查找通常是 O(1)。
> **O(n) 時間複雜度（線性時間）**：表示操作的執行時間與輸入大小成正比，當數據量增加時，操作時間也會線性增加。例如，在 List 中，查找操作通常是 O(n)。
> **HashMap 的運作原理**：HashMap 使用 Hashing 將 Key 映射到一個固定範圍的索引，這使得在 HashMap 中查找 Key 的時間複雜度為 O(1)。
> **HashSet 的運作原理**：HashSet 內部實際上是一個 HashMap，只是將 Key 與 Value 都設置為同一個對象，並將 Value 設置為一個固定的常數。這樣，HashSet 就可以利用 HashMap 的 Hashing 來快速查找對象。

- **數據完整性驗證**：因為 Hashing 是單向的，即從 Hash 值想要推算出原始數據幾乎不可能，因此若是想要通過修改數據來得到相同的 Hash 值，這是非常困難的。這使得 Hashing 可以用於驗證數據完整性，確保數據未被篡改。
- **加密與安全性**：同前一點，Hashing 也被廣泛應用於密碼存儲、數據加密等安全性相關的場景。大多數使用的 Hash 演算法是使用 MD5、SHA-1、SHA-256 等。只要有一個字元的變化，Hash 值就會完全不同。
- **唯一性與識別**：從理論上來說，Hashing 的結果是唯一的，即不同的輸入對應不同的 Hash 值。且使用同一個 Hash 函數。所產出的值的長度是固定的，使得存儲與比對更加高校與方便，很適合作為唯一標識符。如 UUID（Universally Unique Identifier）就是一種基於 Hashing 的唯一標識符。

### **Hashing 的應用**
Hashing 在許多領域中都有廣泛的應用，包括但不限於：
- **集合框架(Java Collections Framework)** 
    - HashMap<K, V>：透過 hashCode() 來決定鍵值對的存儲位置，以提供 O(1) 的查找速度。
    - HashSet<E>：基於 HashMap，使用 Hashing 來確保元素的唯一性，避免重複數據。
    - Hashtable<K, V>：類似 HashMap，但是同步的（Thread-safe）。
- **字串 Hashing**
    - String.hashCode()：用來計算字串的 Hash 值，可用於快速比對字串或作為鍵值對的鍵。
- **物件的唯一性判斷**
    - hashCode() 和 equals() 方法：在集合中使用 Hashing 來確保物件的唯一性，例如存入 HashSet 或作為 HashMap 的鍵。
- **文件完整性驗證**
    - MessageDigest（MD5, SHA-256 等）：用來計算文件的 Hash 值，以驗證文件是否被篡改。
- **分佈式系統**
    - 一致性 Hashing：在分佈式系統（如負載均衡、NoSQL 資料庫）中，透過 Hashing 來分配請求至不同的節點。這個部分的概念好像有點難懂。之後再寫一篇來說明。不過主要的概念就是傳統的 Hashing（如 hash(key) % N）在節點數變動時，可能會導致大量數據重新分佈，影響系統效能。而一致性 Hashing 透過 Hash 環（Hash Ring）解決這個問題，使得節點的增減對數據分佈的影響降到最低。
- **密碼存儲與加密**
    - PBKDF2, BCrypt, Argon2：透過 Hashing 加鹽（Salting）來保護密碼。
- **UUID（Universally Unique Identifier）**
    - UUID.randomUUID().toString()：基於 Hashing 來生成全球唯一的識別碼。
- **緩存與快取機制**
    - ConcurrentHashMap：基於 Hashing 的多執行緒安全的快取資料結構，避免競爭條件（Race Condition）。
- **數據去重**
    - 在大數據應用中，透過 Hashing 來檢查數據是否重複，例如利用 Bloom Filter 來快速判斷數據是否已經出現過。

## 2. Java 中的 Hash 相關 API
- **`hashCode()` 方法**     
    - 永來返回物件的 Hash code，用於確保物件的唯一性。
    - 主要是用在集合框架中，如 HashMap、HashSet。
    - 預設的 `hashCode()` 方法是根據物件的記憶體地址計算的，不同物件的 `hashCode()` 通常不同。而通常我們會需要覆寫 `hashCode()` 方法，以確保物件的唯一性。
    - 範例：
    ```java
    class Person {
        String name;
        int age;

        @Override
        public int hashCode() {
            // 通常我們會使用這個物件中代表唯一性的幾個屬性來計算 hashCode，這樣就可以更加減少碰撞。
            return Objects.hash(name, age);
        }
    }

    public class HashCodeExample {
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.name = "Alice";
            p1.age = 25;

            System.out.println("Hash Code: " + p1.hashCode());
        }
    }
- **`MessageDigest` 類**        
    - MessageDigest 用於產生加密 Hash，如 SHA-256、MD5。
    - 通常用於驗證文件完整性、密碼加密等場景。
    - 加密過後的 Hash 是不可逆的，即無法從 Hash 值反推出原始數據。
    - 也可以當作數位簽章，確保文件的完整性。
    - 範例：
    ```java
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;

    public class MessageDigestExample {
        public static void main(String[] args) throws NoSuchAlgorithmException {
            String input = "HelloWorld";
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hash = md.digest(input.getBytes());

            System.out.println("SHA-256 Hash: " + Arrays.toString(hash));
        }
    }
    ```
- **`SecureRandom` 類**
    - SecureRandom 用於生成安全的隨機數。
    - 通常用於密碼學、安全性相關的場景。
    - SecureRandom 是 Java 安全性提供的隨機數生成器，比 `Random` 類更安全。
    - 範例：
    ```java
    import java.security.SecureRandom;

    public class SecureRandomExample {
        public static void main(String[] args) {
            SecureRandom random = new SecureRandom();
            byte[] bytes = new byte[16];
            random.nextBytes(bytes);

            System.out.println("Secure Random: " + Arrays.toString(bytes));
        }
    }
    ```
- **`Mac` 類**
    - Mac 用於生成 HMAC（Hash-based Message Authentication Code）。
    - HMAC 是一種基於 Hash 的訊息驗證碼，用於驗證數據的完整性和真實性。
    - 通常用於數據通信、網路安全等場景。
    - 範例：
    ```java
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;

    public class MacExample {
        public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
            String input = "HelloWorld";
            String key = "SecretKey";
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
            byte[] hash = mac.doFinal(input.getBytes());

            System.out.println("HMAC-SHA256 Hash: " + Arrays.toString(hash));
        }
    }
    ```
- **`UUID` 類**
    - UUID 用於生成全球唯一的識別碼。
    - UUID 是一種 128 位的數字，通常用於唯一標識符。
    - Java 中的 `UUID.randomUUID()` 可以生成隨機的 UUID。
    - 範例：
    ```java
    import java.util.UUID;

    public class UUIDExample {
        public static void main(String[] args) {
            UUID uuid = UUID.randomUUID();
            System.out.println("UUID: " + uuid);
        }
    }
    ```



## 3. `hashCode()` 與 `equals()` 方法
- **什麼是 `hashCode()`？**
    - `hashCode()` 方法用於計算物件的 Hash 值。
    - Hash 值是一個整數，用於確保物件的唯一性。
- **什麼是 `equals()`？**
    - `equals()` 方法用於比較兩個物件是否相等。
    - 而 `==` 用於比較兩個物件的內存地址。
    - 範例：
    ```java
    public class EqualsExample {
        public static void main(String[] args) {
            String str1 = new String("Hello");
            String str2 = new String("Hello");

            // 比較內存地址
            System.out.println("Using == : " + (str1 == str2)); // false

            // 比較內容
            System.out.println("Using equals() : " + str1.equals(str2)); // true
        }
    }
    ```
- `hashCode()` 與 `equals()` 的關係**
    - 在 Java 中，如果兩個物件相等（`equals()` 返回 `true`），則它們的 `hashCode()` **必須相等**。
    - 這是因為在 Hash 資料結構（如 `HashMap`、`HashSet`）中：
        - `hashCode()` 決定物件應該存放在哪個 **桶（bucket）** 中。
        - `equals()` 用於進一步檢查桶內的物件是否相等（處理 Hash 碰撞）。
    - **重要規則**：
      - **如果 `equals()` 返回 `true`，則 `hashCode()` 必須相等**。
      - **但如果 `hashCode()` 相同，`equals()` 不一定為 `true`（因為可能發生 Hash 碰撞）。**

- 為什麼覆寫 `equals()` 需要覆寫 `hashCode()`？**
    - Java 預設行為
        - `Object` 類別的 `equals()` 預設是比較記憶體位址（`==`）。
        - `Object` 的 `hashCode()` 會基於記憶體位址計算。
    - 如果只覆寫 `equals()` 而不覆寫 `hashCode()`，會導致集合行為異常**
        - 例如 `HashSet` 會用 `hashCode()` 來決定物件的存放位置，若 `hashCode()` 不一致，則即使 `equals()` 相等，物件仍可能存入不同位置，導致集合內部有重複元素。
    - 正確的做法
        - **當覆寫 `equals()` 時，一定要覆寫 `hashCode()`，確保相等的物件有相同的 Hash 值，以保證在 `HashMap`、`HashSet` 等結構中的正確行為。**
    > 小提醒        
    > 在某些類別中，equals 被覆寫了，如 String 因為有 String pool 的緣故，使用 `equals()` 比較特別，要注意一下，這邊就不多說了 

- 為什麼覆寫 `equals()` 需要覆寫 `hashCode()`？
    - 在預設情況下，`equals()` 方法是根據物件的內存地址來比較的，而 `hashCode()` 方法是根據物件的內存地址計算的。
    - 因此，為了保證 `hashCode()` 和 `equals()` 的一致性，我們通常會覆寫這兩個方法。
- 範例：
```java
import java.util.Objects;

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);

        System.out.println(p1.equals(p2)); // true，因為內容相同
        System.out.println(p1.hashCode() == p2.hashCode()); // true，因為覆寫了 hashCode()

        // 放入 HashSet 測試
        java.util.HashSet<Person> set = new java.util.HashSet<>();
        set.add(p1);
        set.add(p2); // 不會被當作重複的物件（若未覆寫 hashCode() 則會出錯）

        System.out.println(set.size()); // 正確結果應為 1
    }
}
```

## 4. Hashing 在 Java Collection Framework 中的應用
- **`HashMap` 如何利用 Hashing 進行資料存取？**
    - HashMap<K, V> 是 Java 中最常用的雜湊結構之一，主要用於 鍵值對（Key-Value） 的存取。其運作方式如下：
        - 將 Key 透過 `hashCode()` 計算出 Hash 值。
        - 將 Hash 值映射到一個固定範圍的索引（桶）Bucket。
        - 將 Key-Value 存入該索引中。
        - 當查找時，再次計算 Key 的 Hash 值，找到對應的索引，取出 Value。
        - 當插入時，若 Bucket 為空，直接存入；若 Bucket 已有元素，則進行碰撞處理（Chaining 或 Open Addressing）。
            - 若鍵相同（equals() 判斷），則更新值。
            - 若鍵不同，則使用 碰撞處理機制（例如 Chaining）。
        - HashMap 會使用與插入相同的 hashCode() 和 index 計算方式來查找鍵值對，因此讀取的時間複雜度通常為 O(1)，但當發生碰撞時，則可能增加到 O(n)。
- **`HashSet` 的運作方式**
    - HashSet<E> 是基於 HashMap 的 Set 實現，用於存儲唯一元素。
    - HashSet 內部實際上是一個 HashMap，只是將 Key 和 Value 都設置為同一個對象，並將 Value 設置為一個固定的常數。
    - HashSet 通過 Hashing 來確保元素的唯一性，避免重複數據。
    - 當我們將元素存入 HashSet 時，HashSet 會將元素作為 Key 存入 HashMap，Value 則設置為一個固定的常數。
    - 由於 HashSet 是透過 HashMap 來運作，因此其效能與 HashMap 相同，即大部分操作的時間複雜度為 O(1)。
- **`Hashtable` vs `HashMap` 的比較**

    |  特性  | HashMap | Hashtable |
    |  ----  | ---- | ---- |
    | 執行緒安全性 | 非同步（非執行緒安全） | 同步（執行緒安全） |
    | 允許 null 值 | 允許 null 作為 Key 或 Value | 不允許 null 作為 Key 或 Value |
    | 效能 | 效能較高（無同步鎖） | 效能較低（每個方法都有同步鎖） |
    | 併發使用建議 | 建議使用 ConcurrentHashMap | 不建議使用，已過時 |

- **碰撞處理技術**（Chaining、Open Addressing）
    - **Chaining 鏈結法**：當發生碰撞時，將相同 Hash 值的元素存入同一個桶中，形成一個鏈表。在 Java 8 之後，當鏈結串列的長度超過 8，並且陣列大小超過 64，則會轉換為 紅黑樹（Red-Black Tree） 來提升效能。
        - 優點：簡單、易於實現。
        - 缺點：當鏈表過長時，查找效率會下降，時間複雜度可能增加到 O(n)。
        - 示意圖：
        ```mermaid
        graph TD;
            A["Index 0"] -->|null| X0
            B["Index 1"] --> A1["A"] --> B1["B"] --> C1["C"]
            D["Index 2"] -->|null| X2
            E["Index 3"] --> D1["D"]

            classDef empty fill:#f0f0f0,stroke:#ccc,stroke-width:1px;
            class X0,X2 empty;
        ```
    - **Open Addressing 開放定址法**：當發生碰撞時，將元素存入其他空桶中，直到找到空桶為止。
        - 優點：不需要額外的空間存儲鏈表。
        - 缺點：容易產生聚集（Clustering）現象，導致查找效率下降。開放定址法可能會導致群聚現象（Clustering），影響查詢效率，因此現代 Java HashMap 採用 Chaining 為主要方式。
        - 示意圖：
        ```mermaid
        graph TD;
        A["Index 0"] --> A1["A"]
        B["Index 1"] --> B1["B"]
        C["Index 2"] -->|Collision!| C1["B'"] 
        D["Index 3"] --> D1["C"]
        
        C1 -.->|Probe Next| C2["B' (Moved)"]
        C2 -.->|Probe Next| C3["B' (Moved)"]
        ```
    - **Rehashing**：當 HashMap 的負載因子（Load Factor）超過一定閾值時，會進行 Rehashing，即將 HashMap 的大小擴大一倍，並重新計算所有元素的 Hash 值。
        - 負載因子 = 元素個數 / 桶的數量
        - 通常負載因子為 0.75，即當元素個數超過桶數量的 75% 時，進行 Rehashing。
        - Rehashing 的時間複雜度為 O(n)，但由於發生的頻率較低，因此平均時間複雜度仍為 O(1)。

## 5. Java 的 Hashing 演算法
- **`String.hashCode()` 的實作方式**
    - 在 Java 中，String 類別的 hashCode() 方法實作了一種簡單的 基於乘法的雜湊函數，其演算法如下：
        - 將字串的每個字元轉換為 Unicode 編碼(ASCII)。
        - 進行乘法運算：`hash = 31 * hash + charAt(i)`。
        - 最後返回 hash 值。
        - 為什麼是 31？
            - 31 是一個質數，且 31 * x = (x << 5) - x，這樣可以用位運算來代替乘法，提高效能。
            - 31 是一個奇數，乘法運算可以保證不會丟失任何訊息。
            - 31 這個值在大多數情況下能夠分布較均勻，減少碰撞機率。
- **`Objects.hash()` 如何計算 Hash 值？**
    - Java 7 引入了 Objects.hash()，用來計算多個物件的 Hash 值，適用於 equals() 需要考慮多個欄位的類別。Objects.hash() 使用的是 變數長度參數（Varargs）。
    - Objects.hash() 與 String.hashCode() 類似，但它可以接受多個變數，並使用 31 作為乘數來降低碰撞機率。
- **進階 Hashing（如 MurmurHash、Guava 的 `Hashing` API）**
    - MurmurHash 是一種非加密的雜湊函數，適用於高效搜尋和數據存取。相較於 String.hashCode()，MurmurHash 具有：
        - 更少的碰撞（Collision）。
        - 更好的分布性（Uniform Distribution）。
        - 比 CRC32 或 MD5 更快。
    - Google Guava 提供了一系列強大的 Hash 演算法，例如：
        - Hashing.murmur3_128() - MurmurHash 128-bit
        - Hashing.sha256() - SHA-256
        - Hashing.md5() - MD5
        - Hashing.farmHashFingerprint64() - Google 的高效雜湊演算法
- 比較
| 方法 | 主要用途 | 優勢 | 可能的缺點 |
|------|--------|-----|----------|
| **`String.hashCode()`** | `String` 的 Hash 計算 | 快速，簡單，內建於 Java | 容易產生碰撞 |
| **`Objects.hash()`** | 多個變數的 Hash 計算 | 方便計算複合鍵 | 效能稍慢（需額外創建陣列） |
| **MurmurHash** | 高效 Hash，適用於大數據 | 分布均勻，低碰撞率 | 需要額外的依賴庫 |
| **Guava `Hashing` API** | 可選 SHA256、Murmur、MD5 等 | 內建多種強大演算法 | 需要引入 Google Guava |

## 7. Hashing 的潛在問題與最佳實踐
- **Hash 碰撞（Collision）問題**
- **低效能 Hash 函數的影響**
- **如何選擇良好的 Hash 函數？**
- **Hashing 安全性考量（防止 DoS 攻擊）**

## 8. Java 內建加密 Hashing（進階）
- **`MessageDigest`（SHA-256, MD5）**
    - MD5 已經不安全，不建議使用。容易被彩虹表攻擊。
    - 範例：
    ```java
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;

    public class SHA256Example {
        public static void main(String[] args) throws NoSuchAlgorithmException {
            String input = "hello";
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes());

            System.out.println(Arrays.toString(hash)); // 以 byte 陣列形式顯示雜湊值
        }
    }
    ```
- **`SecureRandom` 生成安全隨機數**
    - SecureRandom 是 Java 內建的安全隨機數生成器，適用於 加密金鑰生成、密碼鹽（Salt） 等應用。
- **`Mac` 進行 HMAC 驗證**
    - Mac（Message Authentication Code）適合用於 API 簽名和 JWT 簽名驗證的原因在於它能夠提供數據的完整性和真實性驗證。Mac 使用一個密鑰來生成基於 Hash 的訊息驗證碼（HMAC），這樣可以確保數據在傳輸過程中未被篡改，並且只有持有密鑰的雙方才能生成和驗證簽名。因為以下的特性，所以是何作為雙方 API 之間的驗證。
        - 完整性：HMAC 可以確保數據在傳輸過程中未被篡改。如果數據被修改，驗證時生成的 HMAC 將不匹配。
        - 真實性：只有持有密鑰的雙方才能生成和驗證 HMAC，確保數據的來源是可信的。
        - 安全性：HMAC 結合了 Hash 函數和密鑰，提供了比單純的 Hash 函數更高的安全性。
    - 請求端生成 API 簽名範例：
    ```java
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    import java.util.Map;

    public class HmacClient {
        private static final String SECRET_KEY = "superSecretKey123";

        public static String generateHmacSignature(String method, String path, Map<String, String> params) throws Exception {
            // 構建要簽名的數據（例如：請求方法 + 路徑 + 參數）
            StringBuilder data = new StringBuilder();
            data.append(method).append(path);
            params.forEach((key, value) -> data.append(key).append("=").append(value).append("&"));

            // 使用 HMAC-SHA256 產生簽名
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "HmacSHA256");
            mac.init(secretKey);

            byte[] hmacBytes = mac.doFinal(data.toString().getBytes());
            return Base64.getEncoder().encodeToString(hmacBytes); // 轉為 Base64 字串
        }

        public static void main(String[] args) throws Exception {
            String method = "GET";
            String path = "/api/data";
            Map<String, String> params = Map.of("user", "alice", "timestamp", "1710000000");

            String signature = generateHmacSignature(method, path, params);
            System.out.println("Generated Signature: " + signature);
        }
    }
    ```
    - 送出 Request，並把剛剛生成的簽名放在 Header 中，Server 端就可以透過相同的方法驗證簽名。
    ```text
    GET /api/data?user=alice&timestamp=1710000000
    Authorization: HMAC-SHA256 {signature}
    ```
    - 接收端驗證 API 簽名範例：
    ```java
    import org.springframework.web.bind.annotation.*;
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    import java.util.Map;

    @RestController
    @RequestMapping("/api")
    public class HmacServerController {
        private static final String SECRET_KEY = "superSecretKey123";

        @GetMapping("/data")
        public String getData(
            @RequestParam String user,
            @RequestParam String timestamp,
            @RequestHeader("Authorization") String authHeader
        ) throws Exception {
            // 取得客戶端傳來的 HMAC 簽名
            String receivedSignature = authHeader.replace("HMAC-SHA256 ", "");

            // 重新計算 HMAC
            String recalculatedSignature = generateHmacSignature("GET", "/api/data", Map.of("user", user, "timestamp", timestamp));

            // 驗證簽名是否正確
            if (!receivedSignature.equals(recalculatedSignature)) {
                return "401 Unauthorized - Signature mismatch!";
            }

            return "Hello " + user + ", your request is verified!";
        }

        private String generateHmacSignature(String method, String path, Map<String, String> params) throws Exception {
            StringBuilder data = new StringBuilder();
            data.append(method).append(path);
            params.forEach((key, value) -> data.append(key).append("=").append(value).append("&"));

            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "HmacSHA256");
            mac.init(secretKey);

            byte[] hmacBytes = mac.doFinal(data.toString().getBytes());
            return Base64.getEncoder().encodeToString(hmacBytes);
        }
    }
    ```

## 9. 通常我們都怎麼做會員系統的密碼加密？
1. 使用者輸入密碼
2. 系統產生一個隨機的「鹽」（Salt）
3. 將密碼與鹽結合後進行 Hashing
4. 將「鹽」和「Hash 後的密碼」存入資料庫
5. 使用者輸入密碼
6. 從資料庫查出「該使用者的鹽」及「存儲的 Hash 密碼」
7. 將「使用者輸入的密碼」與「鹽」組合後重新 Hash
8. 比較重新 Hash 的結果與資料庫中的 Hash 密碼
9. 若匹配，則登入成功；否則登入失敗
```java
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class PasswordHashing {
    private static final int ITERATIONS = 10000;
    private static final int KEY_LENGTH = 256;

    public static String generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return Base64.getEncoder().encodeToString(salt);
    }

    public static String hashPassword(String password, String salt) throws Exception {
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), Base64.getDecoder().decode(salt), ITERATIONS, KEY_LENGTH);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] hash = factory.generateSecret(spec).getEncoded();
        return Base64.getEncoder().encodeToString(hash);
    }

    public static void main(String[] args) throws Exception {
        String password = "MySecurePassword";
        String salt = generateSalt();

        String hashedPassword = hashPassword(password, salt);
        System.out.println("Salt: " + salt);
        System.out.println("Hashed Password: " + hashedPassword);
    }
}
```
- 掌握幾個心法重點
    - 不可逆（Irreversible）
        - Hashing 是 單向函數，意味著無法從 Hash 值反推出原始密碼。
        - 這與加密（Encryption）不同，加密是可逆的（可以用密鑰解密），而 Hashing 不能解密。
    - 相同輸入 → 相同輸出
        - 如果輸入的密碼和鹽相同，則 Hash 值一定相同。
        - 不同輸入，即使只改變一個字元，Hash 值也會完全不同（雪崩效應）。 

###### tags: `Java` `Hashing` `Security` `Encryption` `Hash` `Salt` `PBKDF2` `SecureRandom` `MessageDigest` `Mac` `UUID` 
