開始之前,先同步幾點觀念:

  1. 系統保存密碼時,應該用雜湊而非加密,雜湊是個單向函數,文字可以轉成雜湊,但無法從雜湊還轉回原始文字。如此可確保任何人(包含系統管理者)都無法解密拿到密碼明碼,但卻又可以判斷輸入文字是否跟密碼一致。
    換言之,若有某個系統的「忘記密碼」居然能把上回設定的密碼寄給你,該系統的安全防護很可能是酥皮等級,別用,快逃~
    延伸閱讀:密碼要怎麼儲存才安全?該加多少鹽?-科普角度
  2. 所讀的暴力破解密碼只適用「攻擊者已進展到從資料庫、記憶體、檔案系統取得雜湊過的密碼內容,可帶回家泡杯茶慢慢破解的情境」,若還擋在線上登入階段,則有多因素驗證、錯誤三次鎖帳號、連續錯誤鎖 IP... 等諸多手段可以有效保護密碼。
    雜湊被偷走的情況多嗎?先不提駭客常會設法拿到雜湊打開大門,站方整批流出也時有所聞(例如:2021 Thingiverse 就曾流出一批 "unsalted SHA-1 or bcrypt password hashes"),硬說不必擔心暴力破解多少有點鴕鳥心態。
  3. 我們常用的 SHA256 雜湊用於檔案比對、數位簽章已足夠,但拿來保存密碼便顯得不夠安全。要儲存密碼,建議還是要用專屬雜湊演算法。(註:更好的做法是別自建帳密管理系統,依賴 Google、Microsfot、Github、FB... 等的身分驗證機制)
    延伸閱讀:六位複雜密碼用 SHA256 面對 5080 擋不過一分鐘

因此,當需要用雜湊保存密碼,選用高破解難度的密碼用雜湊演算法,會比用 SHA256 這類通用雜湊更安全。目前密碼學界普遍認可的幾種密碼用雜湊有以下幾種:

演算法發展年份記憶體需求抗攻擊能力效能特性適用場景
Argon2id2015可調整 (19-46 MiB)最強,抗 GPU/ASIC/側通道攻擊CPU 與記憶體平衡新系統、高安全需求
bcrypt1999固定 4 KB良好,但易受 FPGA 攻擊穩定、跨平台一致一般網路應用、舊系統
scrypt2009可調整 (8-128 MiB)強,特別抗硬體攻擊高記憶體消耗加密貨幣、高安全系統
PBKDF22000最弱,易受 GPU/ASIC 攻擊最快但不利密碼儲存FIPS 合規需求

bcrypt 的主要問題在 4 KB 記憶體需求太低,易被 FPGA (Field-Programmable Gate Array,現場可程式化邏輯閘陣列)之類的硬體用高速平行運算暴力破解。scrypt 對記憶體有較高需求(8 ~ 128MiB),比 bcrypt 更能抵抗專屬硬體(ASIC)攻擊,但存在觀測記憶體存取進行側通道攻擊 (Side-Channel Attack) 的風險。PBKDF2 的抗攻擊能力相對偏弱,除非規格寫死,一般不建議再使用。

相較之下,2015 年密碼雜湊大賽(PHC)的獲勝者 - Argon2id,結合了 Argon2i (抗側通道攻擊) 與 Argon2d (抗 GPU 攻擊)的優點,是目前最全面性、最推薦的密碼雜湊首選。

認識了當代主流密碼雜湊,沒實際跑個程式總覺少了點什麼?那就來用 .NET 實際玩一回吧。

經訐估,使用純 C# 開發的開源程式庫 Konscious.Security.Cryptography.Argon2,是最多人使用的 Argon2id 程式庫,其支援 .NET Standard 1.3、.NET Framework 4.6+、.NET 6.0+,可適用大部分專案環境。以下是個簡單範例:

using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;

byte[] salt = new byte[16];
using (var rng = RandomNumberGenerator.Create())
{
    rng.GetBytes(salt);
}

byte[] hash = ComputeArgon2idHash("password", salt);
Console.WriteLine("Hash1 =" +BitConverter.ToString(hash));

byte[] newHash = ComputeArgon2idHash("password", salt);
Console.WriteLine("Hash2 =" + BitConverter.ToString(newHash));

// 驗證雜湊時不要直接比較,使用固定時間比較函式防止側通道攻擊
bool isEqual = CryptographicOperations.FixedTimeEquals(hash, newHash);
Console.WriteLine($"Hashes are equal: {isEqual}");

static byte[] ComputeArgon2idHash(string password, byte[] salt)
{
    var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
    {
        DegreeOfParallelism = 8,
        MemorySize = 46 * 1024,
        Iterations = 3,
        Salt = salt
    };

    return argon2.GetBytes(32);
}

實測結果如下,加鹽 (Salt) 能讓同一個密碼產生不同的雜湊值,防止攻擊者使用彩虹表 (Rainbow Table)預先算好可能密碼的雜湊值直接比對,實際應用時記得 Salt 要跟雜湊值一起保存。

而由程式邏輯可知 Salt 長度、DegreeOfParallelism、MemorySize、Iterations 均會影響破解難度。RFC 9106 及 OWASP 有參考值:

  • Salt: 長度 8 到 2^32 - 1 Bytes,16 Bytes 為常用值
  • DegreeOfParallelism:1 ~ 2^24
  • MemorySize:至少 46 MiB、建議 64 MiB
  • Iterations:3 (或 MemorySize 1GiB 配 Iteration 1)

練演完畢。

Explains secure password storage using hashing, compares common algorithms, and demonstrates Argon2id hashing in .NET with recommended parameters.


Comments

Be the first to post a comment

Post a comment