過去一段時間來, 衆多的網站遭遇使用者密碼資料庫洩露事件。層出不窮的類似事件對使用者會造成巨大的影響,因為人們往往習慣在不同網站使用相同的密碼,一家 “暴庫”,全部遭殃。
單向加密
一個簡單的方案是将明文密碼做單向哈希後存儲。
單向雜湊演算法有一個特性,無法通過哈希後的摘要(digest) 恢複原始資料,這也是 “單向” 二字的來源,這一點和所有的加密算法都不同。常用的單向雜湊演算法包括 SHA-256, SHA-1, MD5 等。例如,對密碼“passwordhunter” 進行 SHA-256 哈希後的摘要 (digest) 如下:
“bbed833d2c7805c4bf039b140bec7e7452125a04efa9e0b296395a9b95c2d44c”
可能是 “單向” 二字有誤導性,也可能是上面那串數字唬人,不少人誤以為這種方式很可靠, 其實不然。
單向哈希有兩個特性:
1)從同一個密碼進行單向哈希,得到的總是唯一确定的摘要
2)計算速度快。随着技術進步,尤其是顯示卡在高性能計算中的普及,一秒鐘能夠完成數十億次單向哈希計算
結合上面兩個特點,考慮到多數人所使用的密碼為常見的組合,攻擊者可以将所有密碼的常見組合進行單向哈希,得到一個摘要組合, 然後與資料庫中的摘要進行比對即可獲得對應的密碼。這個摘要組合也被稱為 rainbow table(彩虹表)。
更糟糕的是,一個攻擊者隻要建立上述的rainbow table,可以比對所有的密碼資料庫。仍然等同于一家 “暴庫”,全部遭殃。
加鹽哈希
将明文密碼混入 “随機因素 “,然後進行單向哈希後存儲,也就是所謂的”Salted Hash(加鹽哈希)”。
這個方式相比上面的方案,最大的好處是針對每一個資料庫中的密碼,都需要建立一個完整的 rainbow table 進行比對。 因為兩個同樣使用 “passwordhunter”作為密碼的賬戶,在資料庫中存儲的摘要完全不同。
在 C# 中實作加鹽哈希
早在2016年,MD5 作為雜湊演算法已經不可靠,可以人為制造碰撞,于是本文采用了 SHA256 作為雜湊演算法。同時在哈希前生成了一個 Guid 作為鹽和哈希值拼接在一起:
using System;
using System.Security.Cryptography;
using System.Text;
public class PasswordHasher
{
public string HashPassword(string password)
{
var rnd = Guid.NewGuid().ToString("N").Substring(10);
return BuildHash(rnd, password);
}
public bool CheckPassword(string password, string hash)
{
if (string.IsNullOrWhiteSpace(hash))
{
return false;
}
var items = hash.Split('|');
if (items.Length != 2)
{
return false;
}
var rnd = items[0];
return hash == BuildHash(rnd, password);
}
private string BuildHash(string rnd, string password)
{
var key = rnd + "|" + password.Trim();
var hash = Hash(key);
return rnd + "|" + hash;
}
private string Hash(string input)
{
using (var sha = new SHA256CryptoServiceProvider())
{
var bytes = Encoding.UTF8.GetBytes(input);
bytes = sha.ComputeHash(bytes);
return Convert.ToBase64String(bytes);
}
}
}
生成加鹽哈希值:
//生成加鹽哈希
var hasher = new PasswordHasher();
var pwd = hasher.HashPassword("123456");
Console.WriteLine("加鹽哈希值為:{0}",pwd);
校驗密碼是否比對:
//校驗密碼是否比對
var hasher = new PasswordHasher();
var hash = "89455bb276f037799fea1d|1rcfw+tSKhpG7zuW7Sm6SuMgjafAwsMg76OlyFkXLm8=";
var pwd = "123456";
if (hasher.CheckPassword(pwd, hash))
{
Console.WriteLine("密碼正确");
}
else
{
Console.WriteLine("密碼不比對");
}