# 如何安全的加密使用者的密碼 使用 PBKDF2 以及破解的可能性
## 問題
使用者的密碼不得使用明文儲存於資料庫,避免資料庫管理員看的到密碼
同時密文也不可以使用可逆向解密的演算法,避免資料庫外洩後被解出來
為了達成上述兩個目的,會使用 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
###### tags: `資安`