Argon2 是一種密碼雜湊 (password hashing) 演算法,也是 2015 年密碼雜湊競賽(PHC, Password Hashing Competition) 的獲勝者。它被設計來安全地儲存密碼,抵抗暴力破解和 GPU / ASIC 攻擊。
特性
Memory-hard:需要大量記憶體計算,增加硬體破解成本。
Time-cost adjustable:可設定運算次數(iterations),增加雜湊延遲。
Parallelism:支援多執行緒並行計算。
三種變體:
Argon2d:可抵抗 GPU 破解,記憶體存取取決於輸入密碼,但對側信道攻擊 side‑channel attack 敏感。Argon2i:可抵抗側信道攻擊,使用固定存取模式,但稍慢。Argon2id:混合模式,推薦用於大多數應用(兼顧 GPU 攻擊與側信道安全)。
側信道攻擊 side‑channel attack
側信道攻擊不是直接破解演算法數學弱點,而是利用系統在執行時「洩漏的額外資訊」來恢復密碼、金鑰。這些洩漏資訊稱為「側信道」,常見類型有:
時間(Timing):根據運算花費時間推斷內部邏輯或資料(例如不同輸入導致不同記憶體存取時間)。
快取/記憶體訪問模式(Cache / Memory access):透過觀察 CPU cache 命中/未命中或記憶體訪問順序推測密碼相關資料。
電力(Power analysis):量測設備耗電曲線(特別在智慧卡、嵌入式裝置)來推斷內部運算。
電磁輻射(EM):接收設備發出的電磁洩漏訊號。
分支/投機執行漏洞(Spectre/Speculative exec):利用微架構行為取得不該看到的記憶體內容。
I/O 或錯誤回應差異:例如錯誤訊息長短或序列差異可洩漏資訊。
側信道攻擊常見條件:攻擊者和目標在同一台機器或共用硬體資源(例如 cloud 共宿主、虛擬機、瀏覽器 JS 高解析計時)或在物理近距離(電力/EM)時更容易成功。遠端網路時延雜訊大,攻擊難度上升但並非不可能(需大量樣本與高解析度計時)。
若攻擊者只有透過網路遠端(無共用硬體且無高解析計時),側信道攻擊較難。
三種變體
在記憶體存取模式上不同
Argon2d
記憶體存取依賴輸入(data‑dependent)。
優點:對 GPU / ASIC 破解更抗(memory‑hard),但容易洩漏記憶體訪問模式,因此對於有本地或共宿主攻擊者(能觀察 cache/access pattern)的系統 較不安全。
Argon2i
記憶體存取獨立於輸入(data‑independent),以固定/預定方式存取記憶體。
優點:較能抵抗側信道攻擊(尤其是記憶體訪問/快取型);缺點:通常較慢,對某些攻擊(GPU 的大量平行 brute force)耐性較弱。
Argon2id
混合模式:先用 Argon2i 的 data‑independent pass 再用 Argon2d 的 data‑dependent pass(或類似組合)。
建議:大多數情況使用 Argon2id —— 在兼顧側信道與 GPU 抗性間取得平衡,是常見推薦選擇。
java example
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.82</version>
</dependency>
Argon2BouncyCastleExample.java
import org.bouncycastle.crypto.params.Argon2Parameters;
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Arrays;
public class Argon2BouncyCastleExample {
// 產生隨機 salt
private static byte[] generateSalt(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return salt;
}
// 將密碼 hash
public static String hashPassword(String password, byte[] salt) {
// 設定參數
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withSalt(salt)
.withIterations(3) // 運算次數
.withMemoryAsKB(65536) // 記憶體使用量 KB (64 MB)
.withParallelism(2); // 併行線程數
Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(builder.build());
byte[] hash = new byte[32]; // 32 bytes hash
generator.generateBytes(password.toCharArray(), hash, 0, hash.length);
// 回傳 base64 編碼
return Base64.getEncoder().encodeToString(hash);
}
// 驗證密碼
public static boolean verifyPassword(String password, String expectedHashBase64, byte[] salt) {
String hashToCheck = hashPassword(password, salt);
return Arrays.equals(Base64.getDecoder().decode(hashToCheck), Base64.getDecoder().decode(expectedHashBase64));
}
public static void main(String[] args) {
String password = "MyPassword123!";
byte[] salt = generateSalt(16); // 16 bytes salt
// hash
String hash = hashPassword(password, salt);
System.out.println("Salt (Base64): " + Base64.getEncoder().encodeToString(salt));
System.out.println("Hash (Base64): " + hash);
// 驗證
boolean ok = verifyPassword(password, hash, salt);
System.out.println("Password verified: " + ok);
// 測試錯誤密碼
boolean fail = verifyPassword("wrongPassword", hash, salt);
System.out.println("Wrong password verified: " + fail);
}
}
執行結果
Salt (Base64): Uivew8iBmhiZaz7Wv2nSDw==
Hash (Base64): Ldm2DksAcnhUIHyJ4v4uzJJwLd5A2YIhTMn3277/tDU=
Password verified: true
Wrong password verified: false
沒有留言:
張貼留言