# 如何安全的加密使用者的密碼 使用 PBKDF2 ###### tags: `資安` ## 問題 使用者的密碼不得使用明文儲存於資料庫,避免資料庫管理員看的到密碼 同時密文也不可以使用可逆向解密的演算法,避免資料庫外洩後被解出來 為了達成上述兩個目的,會使用 hash 類的演算法,破壞可逆性後儲存 傳統的hash演算法 (如 md5) 已於2016被認定為不安全的 hash 演算法了,坊間有多個 md5 線上比對的網站,可以用下列的網站先產生md5,再拿去對照的網站測試結果,就會發現弱密碼幾乎都可以被反向解回來 * https://www.md5hashgenerator.com/ 線上使用md5產生hash值 * https://crackstation.net/ 雖然md5不能反解,但這個網站已經算好一堆md5 hash的對照資料了 * https://md5dz.wordpress.com/2014/01/16/md5dz-list65/ 同上,也有md5對照資料 ## md5 以外的 hash 演算法 md5 之後較新的演算法是 sha 演算法,可以搭配加鹽的方式避免碰撞攻擊,但 sha1 也已經在2017年被驗證可以被碰撞攻擊了,所以至少要使用 sha256 以上 但sha256也有其他問題,因為電腦運算速度一直提升,難保有一天電腦效能增加,sha256也被攻破 ## PBKDF2 演算法介紹 PBKDF2 演算法是一種實現 **密鑰延伸(key stretching)** 類型的演算法,這類型的演算法是為了對抗電腦運算速度太快,避免密碼被暴力破解的解決方案。 PBKDF2會先使用 hash 類型的演算法,將 hash 的結果當成**hash 演算的鹽**,重新再跑一次 hash,反覆遞迴運算。 因此只要將遞迴 hash 的次數調升,就可以輕鬆地讓運算速度降低,減緩運算速度快的破解時間。 ## PBKDF2 + SHA 雜湊的不可逆演算法,適用於把使用者密碼加密後存入資料庫 http://vegeee-csharp.blogspot.com/2019/05/password-hash-using-pbkdf2-with-hmac.html?m=1 sha = 不可逆雜湊,加密速度快,但運算位數少時容易被碰撞攻擊 pdbdf2 = 可以把演算法加鹽並多跑n次遞回的演算法,速度慢 pbkdf2 + sha = 把sha演算法跑很多次,變成慢速不可逆演算法 ## 完整程式碼 也可以點此觀看執行結果範例 https://dotnetfiddle.net/JQX1AK ```C#= using System; using System.Security.Cryptography; namespace pbkdf2_sha2 { class PasswordHash { private const int SaltByteSize = 32; private const int HashByteSize = 32; private const int Iterations = 4096; private static string GetSalt() { var cryptoProvider = new RNGCryptoServiceProvider(); byte[] b_salt = new byte[SaltByteSize]; cryptoProvider.GetBytes(b_salt); return Convert.ToBase64String(b_salt); } public static string GetPasswordHash(string password) { string salt = GetSalt(); byte[] saltBytes = Convert.FromBase64String(salt); byte[] derived; using (var pbkdf2 = new Rfc2898DeriveBytes( password, saltBytes, Iterations, HashAlgorithmName.SHA512)) { derived = pbkdf2.GetBytes(HashByteSize); } return string.Format("{0}:{1}:{2}", Iterations, Convert.ToBase64String(derived), Convert.ToBase64String(saltBytes)); } public static bool VerifyPasswordHash(string password, string hash) { try { string[] parts = hash.Split(new char[] { ':' }); byte[] saltBytes = Convert.FromBase64String(parts[2]); byte[] derived; int iterations = Convert.ToInt32(parts[0]); using (var pbkdf2 = new Rfc2898DeriveBytes( password, saltBytes, iterations, HashAlgorithmName.SHA512)) { derived = pbkdf2.GetBytes(HashByteSize); } string new_hash = string.Format("{0}:{1}:{2}", Iterations, Convert.ToBase64String(derived), Convert.ToBase64String(saltBytes)); return hash == new_hash; } catch { return false; } } } } ``` ### 注意事項 pbkdf2 使用的是 Rfc 2898 規範,他的套件是[Rfc2898DeriveBytes](https://docs.microsoft.com/zh-tw/dotnet/api/system.security.cryptography.rfc2898derivebytes?view=netframework-4.7.2),雖然最小支援度是寫dotnet Framework 4.6,但要升級到dotnet Framework 4.7.2以上才能在直接寫SHA-512這種方式挑選要採用的雜湊演算法 ## PBKDF2 破解的可能性探討 參考 https://kknews.cc/zh-tw/tech/aeoml8j.html PBKDF2 確實有很大的效果,但對於硬體破解,卻無任何對抗措施。 因為 PBKDF2 只是對原函數簡單封裝,多執行幾次而已。如果原函數不能對抗硬體,那麼套一層 PBKDF2 同樣也不能。 儘管單次 PBKDF 不能被拆解,但可以要求多次 PBKDF,並且互相沒有依賴。這樣多線程就能派上用場了。 例如我們對 PBKDF 進行封裝,要求執行 4 次完全獨立的計算,最後再將結果融合到一起: ```=C# function Parall(Password, Salt, ...) -- 該部分可被並行 -- for i = 0 .. 4 DK[i] = PBKDF(Password, Salt + i, ...) ------------------ return Hash(DK) ``` 這樣,我們即可開啟 4 個線程,同時計算這 4 個 PBKDF。 現在就能用 1 秒的時間,獲得之前 4 秒的強度!攻擊者破解時,成本就增加了 4 倍。 ## 其他更能對抗硬體加密的演算法 bcrypt scrypt Argon2 ## PBKDF2加密 https://blog.csdn.net/chenfanglincfl/article/details/46994393 ## 比較小的pbkdf2範例 https://riptutorial.com/zh-TW/csharp/example/10258/%E5%AF%86%E7%A2%BC%E5%93%88%E5%B8%8C%E7%9A%84pbkdf2 內含各種演算法破解速度比較圖表 https://blackie1019.github.io/2019/02/21/Secured-Password-with-PBKDF2-on-C/ aes可逆加密演算法 https://www.cnblogs.com/zxbzl/archive/2013/03/04/2942939.html