// 寫在前面的話和背景php
由於作項目涉及到用戶在瀏覽器或者用戶windows的桌面系統中輸入完用戶名和密碼,發http的rest請求到nodejs server端,server端驗證用戶名和密碼是有效後(能夠請求db或者ldap),繼續後續的業務邏輯操做(好比操做db或者讀取server上的文件),將操做結果返回給請求端。html
上面的一系列操做,好比 java
1. 當用戶輸入密碼(是否要加密,好比salt等), node
2. 密碼傳輸(https?若是是在windows上桌面請求怎麼保證傳輸的安全性?), python
3. nodejs server是否要解密(好比加 salt的密碼?),在作 ladp驗證?仍是 ladp支持加 salt的驗證?git
11. 下面的文章不能回答上面的問題,文章中c#舉例,但至少是對 密碼加密有個基礎或者概念性的瞭解程序員
http://www.cnblogs.com/leoo2sk/archive/2010/10/01/hash-and-encrypt.html
github
2010-10-01 00:09 by T2噬菌體, 44948 閱讀, 46 評論, 收藏, 編輯算法
0、摘要
今天看到吉日嘎拉的一篇關於管理軟件中信息加密和安全的文章,感受很是有實際意義。文中做者從實踐經驗出發,討論了信息管理軟件中如何經過哈希和加密進行數據保護。可是從文章評論中也能夠看出不少朋友對這個方面一些基本概念比較模糊,這樣就容易「照葫蘆畫瓢」,不能根據自身具體狀況靈活選擇和使用各類哈希和加密方式。本文不對哈希和加密作過於深刻的討論,而是對哈希和加密的基本概念和原理進行闡述、比較,並結合具體實踐說明如何選擇哈希和加密算法、如何提升安全性等問題,使朋友們作到「知其然,知其因此然」,這樣就能經過分析具體狀況,靈活運用哈希和加密保護數據。數據庫
一、哈希(Hash)與加密(Encrypt)的區別
在本文開始,我須要首先從直觀層面闡述哈希(Hash)和加密(Encrypt)的區別,由於我見過不少朋友對這兩個概念不是很清晰,容易混淆二者。而正確區別二者是正確選擇和使用哈希與加密的基礎。
歸納來講,哈希(Hash)是將目標文本轉換成具備相同長度的、不可逆的雜湊字符串(或叫作消息摘要),而加密(Encrypt)是將目標文本轉換成具備不一樣長度的、可逆的密文。
具體來講,二者有以下重要區別:
一、哈希算法每每被設計成生成具備相同長度的文本,而加密算法生成的文本長度與明文自己的長度有關。
例如,設咱們有兩段文本:「Microsoft」和「Google」。二者使用某種哈希算法獲得的結果分別爲:「140864078AECA1C7C35B4BEB33C53C34」和「8B36E9207C24C76E6719268E49201D94」,而使用某種加密算法的到的結果分別爲「Njdsptpgu」和「Hpphmf」。能夠看到,哈希的結果具備相同的長度,而加密的結果則長度不一樣。實際上,若是使用相同的哈希算法,不論你的輸入有多麼長,獲得的結果長度是一個常數,而加密算法每每與明文的長度成正比。
二、哈希算法是不可逆的,而加密算法是可逆的。
這裏的不可逆有兩層含義,一是「給定一個哈希結果R,沒有方法將E轉換成原目標文本S」,二是「給定哈希結果R,即便知道一段文本S的哈希結果爲R,也不能斷言當初的目標文本就是S」。其實稍微想一想就知道,哈希是不可能可逆的,由於若是可逆,那麼哈希就是世界上最強悍的壓縮方式了——能將任意大小的文件壓縮成固定大小。
加密則不一樣,給定加密後的密文R,存在一種方法能夠將R肯定的轉換爲加密前的明文S。
這裏先從直觀層面簡單介紹二者的區別,等下文從數學角度對二者作嚴謹描述後,讀者朋友就知道爲何會有這兩個區別了。
二、哈希(Hash)與加密(Encrypt)的數學基礎
從數學角度講,哈希和加密都是一個映射。下面正式定義二者:
一個哈希算法
是一個多對一映射,給定目標文本S,H能夠將其惟一映射爲R,而且對於全部S,R具備相同的長度。因爲是多對一映射,因此H不存在逆映射![S=H^{-1}(R)](http://static.javashuo.com/static/loading.gif)
使得R轉換爲惟一的S。
一個加密算法
是一個一一映射,其中第二個參數叫作加密密鑰,E能夠將給定的明文S結合加密密鑰Ke惟一映射爲密文R,而且存在另外一個一一映射
,能夠結合Kd將密文R惟一映射爲對應明文S,其中Kd叫作解密密鑰。
下圖是哈希和加密過程的圖示:
![](http://static.javashuo.com/static/loading.gif)
有了以上定義,就很清楚爲何會存在上文提到的兩個區別了。因爲哈希算法的定義域是一個無限集合,而值域是一個有限集合,將無限集合映射到有限集合,根據「鴿籠原理(Pigeonhole principle)」,每一個哈希結果都存在無數個可能的目標文本,所以哈希不是一一映射,是不可逆的。
而加密算法是一一映射,所以理論上來講是可逆的。
可是,符合上面兩個定義的映射僅僅能夠被叫作哈希算法和加密算法,但未必是好的哈希和加密,好的哈希和加密每每須要一些附加條件,下面介紹這些內容。
一個設計良好的哈希算法應該很難從哈希結果找到哈希目標文本的碰撞(Collision)。那麼什麼是碰撞呢?對於一個哈希算法H,若是
,則S1和S2互爲碰撞。關於爲何好的哈希須要難以尋找碰撞,在下面講應用的時候會詳解。另外,好的哈希算法應該對於輸入的改變極其敏感,即便輸入有很小的改動,如一億個字符變了一個字符,那麼結果應該大相徑庭。這就是爲何哈希能夠用來檢測軟件的完整性。
一個設計良好的加密算法應該是一個「單向陷門函數(Trapdoor one-way function)」,單向陷門函數的特色是通常狀況下即便知道函數自己也很難將函數的值轉換回函數的自變量,具體到加密也就是說很難從密文獲得明文,雖然從理論上這是可行的,而「陷門」是一個特殊的元素,一旦知道了陷門,則這種逆轉換則很是容易進行,具體到加密算法,陷門就是密鑰。
順便提一句,在加密中,應該保密的僅僅是明文和密鑰。也就是說咱們一般假設攻擊者對加密算法和密文了如指掌,所以加密的安全性應該僅僅依賴於密鑰而不是依賴於假設攻擊者不知道加密算法。
三、哈希(Hash)與加密(Encrypt)在軟件開發中的應用
哈希與加密在現代工程領域應用很是普遍,在計算機領域也發揮了很大做用,這裏咱們僅僅討論在日常的軟件開發中最多見的應用——數據保護。
所謂數據保護,是指在數據庫被非法訪問的狀況下,保護敏感數據不被非法訪問者直接獲取。這是很是有現實意義的,試想一個公司的安保系統數據庫服務器被入侵,入侵者得到了全部數據庫數據的查看權限,若是管理員的口令(Password)被明文保存在數據庫中,則入侵者能夠進入安保系統,將整個公司的安保設施關閉,或者刪除安保系統中全部的信息,這是很是嚴重的後果。可是,若是口令通過良好的哈希或加密,使得入侵者沒法得到口令明文,那麼最多的損失只是被入侵者看到了數據庫中的數據,而入侵者沒法使用管理員身份進入安保系統做惡。
3.一、哈希(Hash)與加密(Encrypt)的選擇
要實現上述的數據保護,能夠選擇使用哈希或加密兩種方式。那麼在何時該選擇哈希、何時該選擇加密呢?
基本原則是:若是被保護數據僅僅用做比較驗證,在之後不須要還原成明文形式,則使用哈希;若是被保護數據在之後須要被還原成明文,則須要使用加密。
例如,你正在作一個系統,你打算當用戶忘記本身的登陸口令時,重置此用戶口令爲一個隨機口令,然後將此隨機口令發給用戶,讓用戶下次使用此口令登陸,則適合使用哈希。實際上不少網站都是這麼作的,想一想你之前登陸過的不少網站,是否是當你忘記口令的時候,網站並非將你忘記的口令發送給你,而是發送給你一個新的、隨機的口令,而後讓你用這個新口令登陸。這是由於你在註冊時輸入的口令被哈希後存儲在數據庫裏,而哈希算法不可逆,因此即便是網站管理員也不可能經過哈希結果復原你的口令,而只能重置口令。
相反,若是你作的系統要求在用戶忘記口令的時候必須將原口令發送給用戶,而不是重置其口令,則必須選擇加密而不是哈希。
3.二、使用簡單的一次哈希(Hash)方法進行數據保護
首先咱們討論使用一次哈希進行數據保護的方法,其原理以下圖所示:
![](http://static.javashuo.com/static/loading.gif)
對上圖我想已無需多言,不少朋友應該使用過相似的哈希方法進行數據保護。當前最經常使用的哈希算法是MD5和SHA1,下面給出在.NET平臺上用C#語言實現MD5和SHA1哈希的代碼,因爲.NET對於這兩個哈希算法已經進行很很好的封裝,所以咱們沒必要本身實現其算法細節,直接調用相應的庫函數便可(實際上MD5和SHA1算法都十分複雜,有興趣的能夠參考維基百科)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
using
System;
using
System.Web.Security;
namespace
HashAndEncrypt
{
/// <summary>
/// 哈希(Hash)工具類
/// </summary>
public
sealed
class
HashHelper
{
/// <summary>
/// 使用MD5算法進行哈希
/// </summary>
/// <param name="source">源字串</param>
/// <returns>雜湊字串</returns>
public
static
string
MD5Hash(
string
source)
{
return
FormsAuthentication.HashPasswordForStoringInConfigFile(source,
"MD5"
);
}
/// <summary>
/// 使用SHA1算法進行哈希
/// </summary>
/// <param name="source">源字串</param>
/// <returns>雜湊字串</returns>
public
static
string
SHA1Hash(
string
source)
{
return
FormsAuthentication.HashPasswordForStoringInConfigFile(source,
"SHA1"
);
}
}
}
|
3.三、對簡單哈希(Hash)的攻擊
下面咱們討論上述的數據保護方法是否安全。
對於哈希的攻擊,主要有尋找碰撞法和窮舉法。
先來講說尋找碰撞法。從哈希自己的定義和上面的數據保護原理圖能夠看出,若是想非法登陸系統,不必定非要獲得註冊時的輸入口令,只要能獲得一個註冊口令的碰撞便可。所以,若是能從雜湊串中分析出一個口令的碰撞,則大功告成。
不過個人意見是,對這種攻擊大可沒必要擔憂,由於目前對於MD5和SHA1並不存在有效地尋找碰撞方法。雖然我國傑出的數學家王小云教授曾經在國際密碼學會議上發佈了對於MD5和SHA1的碰撞尋找改進算法,但這種方法和不少人口中所說的「破解」相去甚遠,其理論目前僅具備數學上的意義,她將破解MD5的預期步驟數從2^80降到了2^69,雖然從數學上下降了好幾個數量級,但2^69對於實際應用來講仍然是一個天文數字,就比如之前須要一億年,如今須要一萬年同樣。
不過這並不意味着使用MD5或SHA1後就萬事大吉了,由於還有一種對於哈希的攻擊方法——窮舉法。通俗來講,就是在一個範圍內,如從000000到999999,將其中全部值一個一個用相同的哈希算法哈希,而後將結果和雜湊串比較,若是相同,則這個值就必定是源字串或源字串的一個碰撞,因而就能夠用這個值非法登陸了。
例如,下文是對MD5的窮舉攻擊的代碼(設攻擊範圍爲000000到999999):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
using
System;
using
System.Web.Security;
namespace
HashAndEncrypt
{
/// <summary>
/// MD5攻擊工具類
/// </summary>
public
sealed
class
MD5AttackHelper
{
/// <summary>
/// 對MD5進行窮舉攻擊
/// </summary>
/// <param name="hashString">雜湊串</param>
/// <returns>雜湊串的源串或源串碰撞(攻擊失敗則返回null)</returns>
public
static
string
AttackMD5(
string
hashString)
{
for
(
int
i = 0; i <= 999999; i++)
{
string
testString = i.ToString();
while
(testString.Length < 6)
testString =
"0"
+ testString;
if
(FormsAuthentication.HashPasswordForStoringInConfigFile(testString,
"MD5"
) == hashString)
return
testString;
}
return
null
;
}
}
}
|
這種看似笨拙的方法,在現實中爆發的能量倒是驚人的,目前幾乎全部的MD5破解機或MD5在線破解都是用這種窮舉法,但就是這種「笨」方法,卻成功破解出不少哈希串。糾其原因,就是至關一部分口令是很是簡單的,如「123456」或「000000」這種口令還有不少人在用,能夠看出,窮舉法是否能成功很大程度上取決於口令的複雜性。由於窮舉法掃描的區間每每是單字符集、規則的區間,或者由字典數據進行組合,所以,若是使用複雜的口令,例如「ASDF#$%uiop.8930」這種變態級口令,窮舉法就很難奏效了。
3.四、對一次哈希(Hash)的改進——多重混合哈希(Hash)
上面說過,若是口令過於簡單,則使用窮舉法能夠頗有效地破解出一次哈希後的雜湊串。若是不想這樣,只有讓用戶使用複雜口令,可是,不少時候咱們並不能強迫用戶,所以,咱們須要想一種辦法,即便用戶使用諸如「000000」這種簡單密碼,也令窮舉法難奏效。其中一種辦法就是使用多重哈希,所謂多重哈希就是使用不一樣的哈希函數配合自定義的Key對口令進行屢次哈希,若是Key很複雜,那麼窮舉法將變得異常艱難。
例如,若是使用下面的混合公式進行哈希:
![R=SHA1(MD5(S)MD5(Key))](http://static.javashuo.com/static/loading.gif)
若是將Key設爲一個極爲複雜的字符串,那麼在攻擊者不知道Key的狀況下,幾乎沒法經過窮舉法破解。由於即便S很簡單,可是Key的MD5值幾乎是沒法在合理時間內窮舉完的。下面是這種多重混合哈希的代碼實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using
System;
using
System.Web.Security;
namespace
HashAndEncrypt
{
/// <summary>
/// 多重混合哈希工具類
/// </summary>
public
sealed
class
HashHelper
{
private
static
readonly
String hashKey =
"qwer#&^Buaa06"
;
/// <summary>
/// 對敏感數據進行多重混合哈希
/// </summary>
/// <param name="source">待處理明文</param>
/// <returns>Hasn後的數據</returns>
public
static
String Hash(String source)
{
String hashCode = FormsAuthentication.HashPasswordForStoringInConfigFile(source,
"MD5"
) +
FormsAuthentication.HashPasswordForStoringInConfigFile(hashKey,
"MD5"
);
return
FormsAuthentication.HashPasswordForStoringInConfigFile(hashCode,
"SHA1"
);
}
}
}
|
3.五、使用加密(Encrypt)方法進行數據保護
加密方法若是用於口令保護的話,與上述哈希方法的流程基本一致,只是在須要時,能夠使用解密方法獲得明文。關於加密自己是一個很是龐大的系統,而對於加密算法的攻擊更是能夠寫好幾本書了,因此這裏從略。下面只給出使用C#進行DES加密和解密的代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
using
System;
using
System.Security.Cryptography;
using
System.Text;
using
System.Web.Security;
namespace
HashAndEncrypt
{
/// <summary>
/// 工具類,封裝了加解密相關操做
/// </summary>
public
sealed
class
EncryptHelper
{
private
static
readonly
Byte[] DesKey = {5, 7, 8, 9, 0, 2, 1, 6};
private
static
readonly
Byte[] DesVi = { 6, 9, 8, 5, 1, 6, 2, 8 };
/// <summary>
/// 使用DES算法加密數據
/// </summary>
/// <param name="data">待加密數據</param>
/// <returns>密文</returns>
public
static
String Encrypt(String data)
{
DESCryptoServiceProvider des =
new
DESCryptoServiceProvider();
Encoding utf =
new
UTF8Encoding();
ICryptoTransform encryptor = des.CreateEncryptor(DesKey, DesVi);
byte
[] bData = utf.GetBytes(data);
byte
[] bEnc = encryptor.TransformFinalBlock(bData, 0, bData.Length);
return
Convert.ToBase64String(bEnc);
}
/// <summary>
/// 使用DES算法解密數據
/// </summary>
/// <param name="data">待解密數據</param>
/// <returns>明文</returns>
public
static
String Decrypt(String data)
{
DESCryptoServiceProvider des =
new
DESCryptoServiceProvider();
Encoding utf =
new
UTF8Encoding();
ICryptoTransform decryptor = des.CreateDecryptor(DesKey, DesVi);
byte
[] bEnc = Convert.FromBase64String(data);
byte
[] bDec = decryptor.TransformFinalBlock(bEnc, 0, bEnc.Length);
return
utf.GetString(bDec);
}
}
}
|
四、總結
密碼學自己是一個很是深奧的數學分支,對於普通開發者,不須要了解過於深刻的密碼學知識。本文僅僅講述哈希與加密的基礎內容,並對二者作了比較,幫助讀者明晰概念,另外,對一些實際應用狀況進行了簡單的討論。但願本文對你們有所幫助。看了下時間,零點剛過,祝你們十一快樂!玩得開心!
__________________________________________________________________________________________________________
__________________________________________________________________________________________________________
12: 【轉載】 加鹽密碼哈希:如何正確使用
http://blog.jobbole.com/61872/
若是你是Web開發者,你極可能須要開發一個用戶帳戶系統。這個系統最重要的方面,就是怎樣保護用戶的密碼。存放賬號的數據庫常常成爲入侵的目標,因此你必須作點什麼來保護密碼,以防網站被攻破時發生危險。最好的辦法就是對密碼進行加鹽哈希,這篇文章將介紹它是如何作到這點。
在對密碼進行哈希加密的問題上,人們有許多爭論和誤解,這大概是因爲網絡上普遍的誤傳吧。密碼哈希是一件很是簡單的事情,可是依然有不少人理解錯誤了。本文闡述的並非進行密碼哈希惟一正確的方法,可是會告訴你爲何這樣是正確的。
鄭重警告:若是你在試圖編寫本身的密碼哈希代碼,趕忙停下來!那太容易搞砸了。即便你受過密碼學的高等教育,也應該遵從這個警告。這是對全部人說的:不要本身寫加密函數!安全存儲密碼的難題如今已經被解決了,請使用phpass或者本文給出的一些源代碼。
若是由於某些緣由你忽視了上面那個紅色警告,請翻回去好好讀一遍,我是認真的。這篇文章的目的不是教你研究出本身的安全算法,而是講解爲何密碼應該被這樣儲存。
下面一些連接能夠用來快速跳轉到本文的各章節。
- 爲何密碼須要進行哈希?
- 如何破解哈希加密
- 加鹽
- 無效的哈希方法
- 恰當使用哈希加密
- 常見問題
這裏也給出了一些基於BSD許可的哈希函數源代碼:
爲何密碼須要進行哈希?
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542
哈希算法是一個單向函數。它能夠將任何大小的數據轉化爲定長的「指紋」,而且沒法被反向計算。另外,即便數據源只改動了一丁點,哈希的結果也會徹底不一樣(參考上面的例子)。這樣的特性使得它很是適合用於保存密碼,由於咱們須要加密後的密碼沒法被解密,同時也能保證正確校驗每一個用戶的密碼。
在基於哈希加密的帳戶系統中,一般用戶註冊和認證的流程是這樣的:
- 用戶註冊一個賬號
- 密碼通過哈希加密儲存在數據庫中。只要密碼被寫入磁盤,任什麼時候候都不容許是明文
- 當用戶登陸的時候,從數據庫取出已經加密的密碼,和通過哈希的用戶輸入進行對比
- 若是哈希值相同,用戶得到登入受權,不然,會被告知輸入了無效的登陸信息
- 每當有用戶嘗試登陸,以上兩步都會重複
在第4步中,永遠不要告訴用戶究竟是用戶名錯了,仍是密碼錯了。只須要給出一個大概的提示,好比「無效的用戶名或密碼」。這能夠防止攻擊者在不知道密碼的狀況下,枚舉出有效的用戶名。
須要提到的是,用於保護密碼的哈希函數和你在數據結構中學到的哈希函數是不一樣的。好比用於實現哈希表這之類數據結構的哈希函數,它們的目標是快速查找,而不是高安全性。只有加密哈希函數才能用於保護密碼,例如SHA256,SHA512,RipeMD和WHIRLPOOL。
也許你很容易就認爲只須要簡單地執行一遍加密哈希函數,密碼就能安全,那麼你大錯特錯了。有太多的辦法能夠快速地把密碼從簡單哈希值中恢復出來,但也有不少比較容易實現的技術能使攻擊者的效率大大下降。黑客的進步也在激勵着這些技術的進步,好比這樣一個網站:你能夠提交一系列待破解的哈希值,而且在不到1秒的時間內獲得告終果。顯然,簡單哈希加密並不能知足咱們對安全性的需求。
那麼下一節會講到幾種經常使用的破解簡單哈希加密的辦法。
如何破解哈希加密
字典攻擊和暴力攻擊
Dictionary Attack
Trying apple : failed
Trying blueberry : failed
Trying justinbeiber : failed
...
Trying letmein : failed
Trying s3cr3t : success!
Brute Force Attack
Trying aaaa : failed
Trying aaab : failed
Trying aaac : failed
...
Trying acdb : failed
Trying acdc : success!
• 破解哈希加密最簡單的辦法,就是去猜,將每一個猜想值哈希以後的結果和目標值比對,若是相同則破解成功。兩種最多見的猜密碼的辦法是字典攻擊和暴力攻擊。
• 字典攻擊須要使用一個字典文件,它包含單詞、短語、經常使用密碼以及其餘可能用做密碼的字符串。其中每一個詞都是進過哈希後儲存的,用它們和密碼哈希比對,若是相同,這個詞就是密碼。字典文件的構成是從大段文本中分解出的單詞,甚至還包括一些數據庫中真實的密碼。而後還能夠對字典文件進行更進一步的處理使它更有效,好比把單詞中的字母替換爲它們的「形近字」(hello變爲h3110)。
• 暴力攻擊會嘗試每個在給定長度下各類字符的組合。這種攻擊會消耗大量的計算,也一般是破解哈希加密中效率最低的辦法,可是它最終會找到正確的密碼。所以密碼須要足夠長,以致於遍歷全部可能的字符串組合將耗費太長時間,從而不值得去破解它。
• 咱們沒有辦法阻止字典攻擊和暴擊攻擊,儘管能夠下降它們的效率,但那也不是徹底阻止。若是你的密碼哈希系統足夠安全,惟一的破解辦法就是進行字典攻擊或者暴力遍歷每個哈希值。
查表法
Searching: 5f4dcc3b5aa765d61d8327deb882cf99: FOUND: password5
Searching: 6cbe615c106f422d23669b610b564800: not in database
Searching: 630bf032efe4507f2c57b280995925a9: FOUND: letMEin12
Searching: 386f43fab5d096a7a66d67c8f213e5ec: FOUND: mcd0nalds
Searching: d5ec75d5fe70d428685510fae36492d9: FOUND: p@ssw0rd!
查表法對於破解一系列算法相同的哈希值有着無與倫比的效率。主要的思想就是預計算密碼字典中的每一個密碼,而後把哈希值和對應的密碼儲存到一個用於快速查詢的數據結構中。一個良好的查表實現能夠每秒進行數百次哈希查詢,即便表中儲存了幾十億個哈希值。
若是你想更好地體驗查表法的速度,嘗試使用CrackStation的free hash cracker來破解下圖中四個SHA256加密的哈希值吧。
c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc
08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7
e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904
5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd
反向查表法
Searching for hash(apple) in users' hash list... : Matches [alice3, 0bob0, charles8]
Searching for hash(blueberry) in users' hash list... : Matches [usr10101, timmy, john91]
Searching for hash(letmein) in users' hash list... : Matches [wilson10, dragonslayerX, joe1984]
Searching for hash(s3cr3t) in users' hash list... : Matches [bruce19, knuth1337, john87]
Searching for hash(z@29hjja) in users' hash list... : No users used this password
這種方法能夠使攻擊者同時對多個哈希值發起字典攻擊或暴力攻擊,而不須要預先計算出一個查詢表。
首先攻擊者構造一個基於密碼-用戶名的一對多的表,固然數據須要從某個已經被入侵的數據庫得到,而後猜想一系列哈希值而且從表中查找擁有此密碼的用戶。一般許多用戶可能有着相同的密碼,所以這種攻擊方式也顯得尤其有效。
彩虹表
彩虹表是一種在時間和空間的消耗上找尋平衡的破解技術。它和查表法很相似,可是爲了使查詢表佔用的空間更小而犧牲了破解速度。由於它更小,因而咱們能夠在必定的空間內存儲更多的哈希值,從而使攻擊更加有效。可以破解任何8位及如下長度MD5值的彩虹表已經出現了。
下面咱們會講到一種讓查表法和彩虹表都失去做用的技術,叫作加鹽。
加鹽
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007
查表法和彩虹表只有在全部密碼都以相同方式進行哈希加密時纔有效。若是兩個用戶密碼相同,那麼他們密碼的哈希值也是相同的。咱們能夠經過「隨機化」哈希來阻止這類攻擊,因而當相同的密碼被哈希兩次以後,獲得的值就不相同了。
好比能夠在密碼中混入一段「隨機」的字符串再進行哈希加密,這個被字符串被稱做鹽值。如同上面例子所展現的,這使得同一個密碼每次都被加密爲徹底不一樣的字符串。爲了校驗密碼是否正確,咱們須要儲存鹽值。一般和密碼哈希值一塊兒存放在帳戶數據庫中,或者直接存爲哈希字符串的一部分。
鹽值並不須要保密??,因爲隨機化了哈希值,查表法、反向查表法和彩虹表都再也不有效。攻擊者沒法確知鹽值,因而就不能預先計算出一個查詢表或者彩虹表。這樣每一個用戶的密碼都混入不一樣的鹽值後再進行哈希,所以反向查表法也變得難以實施。
我理解這裏不須要保密的意思是,並非能夠把 鹽值 讓攻擊者知道,
下面講講咱們在實現加鹽哈希的過程當中一般會犯哪些錯誤。
錯誤一:短鹽值和鹽值重複
最多見的錯誤就是在屢次哈希加密中使用相同的鹽值或者過短的鹽值。
鹽值重複
每次哈希加密都使用相同的鹽值是很容易犯的一個錯誤,這個鹽值要麼被硬編碼到程序裏,要麼只在第一次使用時隨機得到。這樣加鹽的方式是作無用功,由於兩個相同的密碼依然會獲得相同的哈希值。攻擊者仍然能夠使用反向查表法對每一個值進行字典攻擊,只須要把鹽值應用到每一個猜想的密碼上再進行哈希便可。若是鹽值被硬編碼到某個流行的軟件裏,能夠專門爲這個軟件製做查詢表和彩虹表,那麼破解它生成的哈希值就變得很簡單了。
用戶建立帳戶或每次修改密碼時,都應該從新生成新的鹽值進行加密。
短鹽值
若是鹽值過短,攻擊者能夠構造一個查詢表包含全部可能的鹽值。以只有3個ASCII字符的鹽值爲例,一共有95x95x95=857,375種可能。這看起來不少,可是若是對於每一個鹽值查詢表只包含1MB最多見的密碼,那麼總共只須要837GB的儲存空間。一個不到100美圓的1000GB硬盤就能解決問題。
一樣地,用戶名也不該該被用做鹽值。儘管在一個網站中用戶名是惟一的,可是它們是可預測的,而且常常重複用於其餘服務中。攻擊者能夠針對常見用戶名構建查詢表,而後對用戶名鹽值哈希發起進攻。
爲了使攻擊者沒法構造包含全部可能鹽值的查詢表,鹽值必須足夠長。一個好的作法是使用和哈希函數輸出的字符串等長的鹽值,好比SHA256算法的輸出是256bits(32 bytes),那麼鹽值也至少應該是32個隨機字節。
錯誤二:兩次哈希和組合哈希函數
(譯註:此節標題原文中的Wacky Hash Functions直譯是古怪的哈希函數,大概是因爲做者不承認這種組合多種哈希函數的作法,爲了便於理解,本文仍是翻譯爲組合哈希函數)
這節講述了另外一種對密碼哈希的誤解:使用組合哈希函數。人們常常情不自禁地認爲將不一樣的哈希函數組合起來,結果會更加安全。實際上這樣作幾乎沒有好處,僅僅形成了函數之間互相影響的問題,甚至有時候會變得更加不安全。永遠不要嘗試發明本身的加密方法,只需只用已經被設計好的標準算法。有的人會說使用多種哈希函數會使計算更慢,從而破解也更慢,可是還有其餘的辦法能更好地減緩破解速度,後面會提到的。
這裏有些低端的組合哈希函數,我在網上某些論壇看到它們被推薦使用:
- md5(sha1(password))
- md5(md5(salt) + md5(password))
- sha1(sha1(password))
- sha1(str_rot13(password + salt))
- md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))
不要使用其中任何一種。
注意:這節內容是有爭議的。我已經收到的大量的郵件,爲組合哈希函數而辯護。他們的理由是若是攻擊者不知道系統使用的哪一種哈希函數,那麼也就很難預先爲這種組合構造出彩虹表,因而破解起來會花費更多的時間。
誠然,攻擊者在不知道加密算法的時候是沒法發動攻擊的,可是不要忘了Kerckhoffs’s principle,攻擊者一般很容易就能拿到源碼(尤爲是那些免費或開源的軟件)。經過系統中取出的一些密碼-哈希值對應關係,很容易反向推導出加密算法。破解組合哈希函數確實須要更多時間,但也只是受了一點能夠確知的因素影響。更好的辦法是使用一個很難被並行計算出結果的迭代算法,而後增長適當的鹽值防止彩虹表攻擊。
固然你實在想用「標準的」組合哈希函數,好比HMAC,也是能夠的。但若是隻是爲了使破解起來更慢,那麼先讀讀下面講到的密鑰擴展。
創造新的哈希函數可能帶來安全問題,構造哈希函數的組合又可能帶來函數間互相影響的問題,它們帶來的一丁點好處和這些比起來真是微不足道。顯然最好的作法是使用標準的、通過完整測試的算法。
哈希碰撞
哈希函數將任意大小的數據轉化爲定長的字符串,所以其中必定有些輸入通過哈希計算以後獲得了相同的結果。加密哈希函數的設計就是爲了使這樣的碰撞儘量難以被發現。隨着時間流逝,密碼學家發現攻擊者愈來愈容易找到碰撞了,最近的例子就是MD5算法的碰撞已經肯定被發現了。
碰撞攻擊的出現代表極可能有一個和用戶密碼不一樣的字符串卻和它有着相同的哈希值。然而,即便在MD5這樣脆弱的哈希函數中找到碰撞也須要耗費大量的計算,所以這樣的碰撞「意外地」在實際中出現的可能性是很低的。因而站在實用性的角度上能夠這麼說,加鹽MD5和加鹽SHA256的安全性是同樣的。不過可能的話,使用自己更安全的哈希函數老是好的,好比SHA25六、SHA5十二、RipeMD或者WHIRLPOOL。
正確的作法:恰當使用哈希加密
本節會準確講述應該如何對密碼進行哈希加密。其中第一部分介紹最基本的要素,也是在哈希加密中必定要作到的;後面講解怎樣在這個基礎上進行擴展,使得加密更難被破解。
基本要素:加鹽哈希
忠告:你不只僅要用眼睛看文章,更要本身動手去實現後面講到的「讓密碼更難破解:慢哈希函數」。
在前文中咱們已經看到,利用查表法和彩虹表,普通哈希加密是多麼容易被惡意攻擊者破解,也知道了能夠經過隨機加鹽的辦法也解決這個問題。那麼到底應該使用怎樣的鹽值呢,又如何把它混入密碼?
鹽值應該使用基於加密的僞隨機數生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)來生成。CSPRNG和普通的隨機數生成器有很大不一樣,如C語言中的rand()函數。物如其名,CSPRNG專門被設計成用於加密,它能提供高度隨機和沒法預測的隨機數。咱們顯然不但願本身的鹽值被猜想到,因此必定要使用CSPRNG。下面的表格列出了當前主流編程語言中的CSPRNG方法:
對於每一個用戶的每一個密碼,鹽值都應該是獨一無二的。每當有新用戶註冊或者修改密碼,都應該使用新的鹽值進行加密。而且這個鹽值也應該足夠長,使得有足夠多的鹽值以供加密。一個好的標準的是:鹽值至少和哈希函數的輸出同樣長;鹽值應該被儲存和密碼哈希一塊兒儲存在帳戶數據表中。
存儲密碼的步驟
- 使用CSPRNG生成一個長度足夠的鹽值
- 將鹽值混入密碼,並使用標準的加密哈希函數進行加密,如SHA256
- 把哈希值和鹽值一塊兒存入數據庫中對應此用戶的那條記錄
校驗密碼的步驟
- 從數據庫取出用戶的密碼哈希值和對應鹽值
- 將鹽值混入用戶輸入的密碼,而且使用一樣的哈希函數進行加密
- 比較上一步的結果和數據庫儲存的哈希值是否相同,若是相同那麼密碼正確,反之密碼錯誤
文章最後有幾個加鹽密碼哈希的代碼實現,分別使用了PHP、C#、Java和Ruby。
在Web程序中,永遠在服務器端進行哈希加密
若是你正在開發一個Web程序,你可能會疑惑到底在哪進行加密。是使用JavaScript在用戶的瀏覽器上操做呢,仍是將密碼「裸體」傳送到服務器再進行加密?
即便瀏覽器端用JavaScript加密了,你仍然須要在服務端再次進行加密。試想有個網站在瀏覽器將密碼通過哈希後傳送到服務器,那麼在認證用戶的時候,網站收到哈希值和數據庫中的值進行比對就能夠了。這看起來比只在服務器端加密安全得多,由於至始至終沒有將用戶的密碼明文傳輸,但實際上不是這樣。
問題在於,從客戶端來看,通過哈希的密碼邏輯上成爲用戶真正的密碼。爲了經過服務器認證,用戶只須要發送密碼的哈希值便可。若是有壞小子獲取了這個哈希值,他甚至能夠在不知道用戶密碼的狀況經過認證。更進一步,若是他用某種手段入侵了網站的數據庫,那麼不須要去猜解任何人的密碼,就能夠隨意使用每一個人的賬號登陸。
這並非說你不該該在瀏覽器端進行加密,可是若是你這麼作了,必定要在服務端再次加密。在瀏覽器中進行哈希加密是個好想法,不過實現的時候注意下面幾點:
• 客戶端密碼哈希並不能代替HTTPS(SSL/TLS)。若是瀏覽器和服務器之間的鏈接是不安全的,那麼中間人攻擊能夠修改JavaScript代碼,刪除加密函數,從而獲取用戶密碼。
• 有些瀏覽器不支持JavaScript,也有的用戶禁用了瀏覽器的JavaScript功能。爲了最好的兼容性,你的程序應該檢測JavaScript是否可用,若是答案爲否,須要在服務端模擬客戶端的加密。
• 客戶端哈希一樣須要加鹽,很顯然的辦法就是向服務器請求用戶的鹽值,可是不要這麼作。由於這給了壞蛋一個機會,可以在不知道密碼的狀況下檢測用戶名是否有效。既然你已經在服務端對密碼進行了加鹽哈希,那麼在客戶端把用戶名(或郵箱)加上網站特有的字符串(如域名)做爲鹽值是可行的。
讓密碼更難破解:慢哈希函數
加鹽使攻擊者沒法採用特定的查詢表和彩虹錶快速破解大量哈希值,可是卻不能阻止他們使用字典攻擊或暴力攻擊。高端的顯卡(GPU)和定製的硬件能夠每秒進行數十億次哈希計算,所以這類攻擊依然能夠很高效。爲了下降攻擊者的效率,咱們能夠使用一種叫作密鑰擴展的技術。
這種技術的思想就是把哈希函數變得很慢,因而即便有着超高性能的GPU或定製硬件,字典攻擊和暴力攻擊也會慢得讓攻擊者沒法接受。最終的目標是把哈希函數的速度降到足以讓攻擊者望而卻步,但形成的延遲又不至於引發用戶的注意。
密鑰擴展的實現是依靠一種CPU密集型哈希函數。不要嘗試本身發明簡單的迭代哈希加密,若是迭代不夠多,是能夠被高效的硬件快速並行計算出來的,就和普通哈希同樣。應該使用標準的算法,好比PBKDF2或者bcrypt。這裏能夠找到PBKDF2在PHP上的一種實現。
這類算法使用一個安全因子或迭代次數做爲參數,這個值決定了哈希函數會有多慢。對於桌面軟件或者手機軟件,獲取參數最好的辦法就是執行一個簡短的性能基準測試,找到使哈希函數大約耗費0.5秒的值。這樣,你的程序就能夠儘量保證安全,而又不影響到用戶體驗。
若是你在一個Web程序中使用密鑰擴展,記得你須要額外的資源處理大量認證請求,而且密鑰擴展也使得網站更容易遭受拒絕服務攻擊(DoS)。但我依然推薦使用密鑰擴展,不過把迭代次數設定得低一點,你應該基於認證請求最高峯時的剩餘硬件資源來計算迭代次數。要求用戶每次登陸時輸入驗證碼能夠消除拒絕服務的威脅。另外,必定要把你的系統設計爲迭代次數可隨時調整的。
若是你擔憂計算量帶來的負載,但又想在Web程序中使用密鑰擴展,能夠考慮在瀏覽器中用JavaScript完成。Stanford JavaScript Crypto Library裏包含了PBKDF2的實現。迭代次數應該被設置到足夠低,以適應速度較慢的客戶端,好比移動設備。同時當客戶端不支持JavaScript的時候,服務端應該接手計算。客戶端的密鑰擴展並不能免除服務端進行哈希加密的職責,你必須對客戶端傳來的哈希值再次進行哈希加密,就像對付一個普通密碼同樣。
沒法破解的哈希加密:密鑰哈希和密碼哈希設備
只要攻擊者能夠檢測對一個密碼的猜想是否正確,那麼他們就能夠進行字典攻擊或暴力攻擊。所以下一步就是向哈希計算中增長一個密鑰,只有知道這個密鑰的人才能校驗密碼。有兩種辦法能夠實現:將哈希值加密,好比使用AES算法;將密鑰包含到哈希字符串中,好比使用密鑰哈希算法HMAC。
聽起來很簡單,作起來就不同了。這個密鑰須要在任何狀況下都不被攻擊者獲取,即便系統由於漏洞被攻破了。若是攻擊者獲取了進入系統的最高權限,那麼不論密鑰被儲存在哪,他們均可以竊取到。所以密鑰須要儲存在外部系統中,好比另外一個用於密碼校驗的物理服務器,或者一個關聯到服務器的特製硬件,如YubiHSM。
我強烈推薦大型服務(10萬用戶以上)使用這類辦法,由於我認爲面對如此多的用戶是有必要的。
若是你難以負擔多個服務器或專用的硬件,仍然有辦法在一個普通Web服務器上利用密鑰哈希技術。大部分針對數據庫的入侵都是因爲SQL注入攻擊,所以不要給攻擊者進入本地文件系統的權限(禁止數據庫服務訪問本地文件系統,若是它有這個功能的話)。這樣一來,當你隨機生成一個密鑰存到經過Web程序沒法訪問的文件中,而後混入加鹽哈希,獲得的哈希值就再也不那麼脆弱了,即使這時數據庫遭受了注入攻擊。不要把將密鑰硬編碼到代碼裏,應該在安裝時隨機生成。這固然不如獨立的硬件系統安全,由於若是Web程序存在SQL注入點,那麼可能還存在其餘一些問題,好比本地文件包含漏洞(Local File Inclusion),攻擊者能夠利用它讀取本地密鑰文件。不管如何,這個措施比沒有好。
請注意密鑰哈希不表明無需進行加鹽。高明的攻擊者早晚會找到辦法竊取密鑰,所以依然對密碼哈希進行加鹽和密鑰擴展很重要。
其餘安全措施
哈希加密能夠在系統發生入侵時保護密碼,但這並不能使整個程序更加安全。首先還有不少事情須要作,來保證密碼哈希(和其餘用戶數據)不被竊取。
即便經驗豐富的開發者也須要額外學習安全知識,才能寫出安全的程序。這裏有個關於Web程序漏洞的資源:The Open Web Application Security Project (OWASP),還有一個很好的介紹:OWASP Top Ten Vulnerability List。除非你瞭解列表中全部的漏洞,才能嘗試編寫一個處理敏感數據的Web程序。僱主也有責任保證他全部的開發人員都有資質編寫安全的程序。
對你的程序進行第三方「滲透測試」是一個不錯的選擇。最好的程序員也可能犯錯,所以有一個安全專家審查你的代碼尋找潛在的漏洞是有意義的。找尋值得信賴的機構(或招聘人員)來對你的代碼進行審查。安全審查應該從編碼的初期就着手進行,一直貫穿整個開發過程。
監控你的網站來發現入侵行爲也是很重要的,我推薦至少僱傭一我的全職負責監測和處理安全隱患。若是有個漏洞沒被發現,攻擊者可能經過網站利用惡意軟件感染訪問者,所以檢測漏洞而且及時應對是十分重要的。
常見問題
我應該使用什麼哈希算法?
應該使用:
不要使用:
- 過期的函數,好比MD5或SHA1
- 不安全的crypt()版本($1$,$2$,$2x$,$3$)
- 任何你本身設計的加密算法。只應該使用那些在公開領域中的,而且被密碼學家完整測試過的技術
儘管尚未一種針對MD5或SHA1很是效率的攻擊手段,可是它們太古老也被普遍地認爲不足以勝任存儲密碼的工做(某種程度上甚至是錯誤的),所以我也不推薦使用它們。可是有個例外,PBKDF2中頻繁地使用了SHA1做爲它底層的哈希函數。
當用戶忘記密碼的時候,怎樣進行重置?
我我的的觀點是,當前全部普遍使用的密碼重置機制都是不安全的。若是你對安全性有極高的要求,好比一個加密服務,那麼不要容許用戶重置密碼。
大多數網站向那些忘記密碼的用戶發送電子郵件來進行身份認證。首先,須要隨機生成一個一次性的令牌,它直接關聯到用戶的帳戶。而後將這個令牌混入一個重置密碼的連接中,發送到用戶的電子郵箱。最後當用戶點擊這個包含有效令牌的連接時,提示他們能夠設置新的密碼。要確保這個令牌只對一個帳戶有效,以防攻擊者從郵箱獲取到令牌後,用來重置其餘用戶的密碼。
令牌必須在15分鐘內使用,而且一旦被使用就當即失效。當用戶從新請求令牌時,或用戶登陸成功時(說明他還記得密碼),使原令牌失效也是一個好作法。若是一個令牌始終不過時,那麼它一直能夠用於入侵用戶的賬號。電子郵件(SMTP)是一個純文本協議,而且網絡上有不少惡意路由在截取郵件信息。在用戶修改密碼後,那些包含重置密碼連接的郵件在很長一段時間內依然缺少保護。所以應該儘早使令牌過時,下降把用戶信息暴露給攻擊者的可能。
攻擊者是能夠篡改令牌的,因此不要把帳戶信息和失效時間存儲在裏面。這些信息應該以不可猜解的二進制形式存在,而且只用來識別數據庫中某條用戶的記錄。
永遠不要經過電子郵件向用戶發送新密碼,同時也記得在用戶重置密碼的時候隨機生成一個新的鹽值用於加密,不要重複使用以前密碼的那個鹽值。
當帳戶數據庫被泄漏或入侵時,應該怎麼作?
你首先須要作的,是查看系統被暴露到什麼程度了,而後修復這個攻擊者利用的漏洞。若是你沒有應對入侵的經驗,我強烈推薦僱一個第三方安全機構來作這件事。
將一個漏洞精心掩蓋期待沒有人能注意到,是否聽起來很省事而又誘人呢?可是這樣只會讓你顯得更糟糕,由於你在用戶不知情的狀況下,將他們的密碼和我的信息暴露在危險之中。即便用戶還沒法理解到底發生了什麼,你也應該儘快履行告知的義務。好比在首頁放置一個連接,指向對此問題更詳細的說明,可能的話還能夠經過電子郵件告知用戶目前的狀況。
向你的用戶說明你是如何保護他們的密碼的——最好是使用了加鹽哈希——即使如此惡意黑客也能使用字典攻擊和暴力攻擊。設想用戶可能在不少服務中使用相同的密碼,攻擊者會用找到的密碼去嘗試登陸其餘網站。提示你的用戶應該修改全部類似的密碼,不論它們被使用在哪一個服務上,而且強制用戶下次登陸你的網站時修改密碼。大部分用戶會嘗試將密碼「修改」爲和以前相同的以便記憶,你應該使用老密碼的哈希值來確保用戶沒法這麼作。
即便有加鹽哈希的保護,攻擊者也極可能快速破解其中一些脆弱的密碼。爲了減小攻擊者使用的它們機會,你應該對這些密碼的賬號發送認證電子郵件,直到用戶修改了密碼。能夠參考上一個問題,其中有一些實現電子郵件認證的要點。
另外也要告訴你的用戶,網站到底儲存了哪些我的信息。若是你的數據庫中有用戶的信用卡號,你應該指導用戶檢查本身近期的帳單,而且註銷掉這張信用卡。
我應該使用什麼樣的密碼規則?是否應該強制用戶使用複雜的密碼?
若是你的服務對安全性沒有嚴格的要求,那麼不要對用戶進行限制。我推薦在用戶輸入密碼的時候,頁面上顯示出密碼強度,由用戶本身決定須要多安全的密碼。若是你的服務對安全有特殊的需求,那就應該強制用戶輸入長度至少爲12個字符的密碼,而且其中至少包括兩個字母、兩個數字和兩個符號。
不要過於頻繁地強制你的用戶修改密碼,最多6個月1次,由於那樣作會使用戶疲於選擇一個強度足夠好的密碼。更好的作法是指導用戶在他們感受密碼可能泄漏的時候去主動修改,而且提示用戶不要把密碼告訴任何人。若是這是在商業環境中,鼓勵你的員工利用工做時間熟記並使用他們的密碼。
若是攻擊者入侵了個人數據庫,他們難道不能把其中的密碼哈希替換爲本身的值,而後登陸系統麼?
固然能夠,可是若是他已經入侵了你的數據庫,那麼極可能已經有權限訪問你服務器上任何東西了,所以徹底不必登陸帳戶去獲取他想要的。對密碼進行哈希加密的手段,(對網站而言)不是保護網站免受入侵,而是在入侵已經發生時保護數據庫中的密碼。
經過爲數據庫鏈接設置兩種權限,能夠防止密碼哈希在遭遇注入攻擊時被篡改。一種權限用於建立用戶:它對用戶表可讀可寫;另外一種用於用戶登陸,它只能讀用戶表而不能寫。
爲何我非得用像HMAC那種特殊的算法?爲何不能簡單地把密鑰混入密碼?
像MD五、SHA1和SHA2這類哈希函數是基於Merkle–Damgård構造的,所以在長度擴展攻擊面前很是脆弱。就是說若是已經知道一個哈希值H(X),對於任意的字符串Y,攻擊者能夠計算出H(pad(X) + Y)的值,而不須要知道X是多少,其中pad(X)是哈希函數的填充函數(padding function,好比MD5將數據每512bit分爲一組,最後不足的將填充字節)。
在攻擊者不知道密鑰(key)的狀況下,他仍然能夠根據哈希值H(key + message)計算出H(pad(key + message) + extension)。若是這個哈希值用於身份認證,而且依靠其中的密鑰來防止攻擊者篡改消息,這個辦法已經行不通了。由於攻擊者無需知道密鑰,也能構造出包含message + extension的一個有效的哈希值。
目前還不清楚攻擊者可否用這個辦法更快破解密碼,可是因爲這種攻擊的出現,在密鑰哈希中使用上述哈希函數已經被認爲是差勁的實踐了。也許某天高明的密碼學家會發現一個利用長度擴展攻擊的新思路,從而更快地破解密碼,因此仍是使用HMAC吧。
鹽值應該加到密碼前面仍是後面?
都行,可是在一個程序中應該保持一致,以避免出現互操做方面的問題。目前看來加到密碼以前是比較經常使用的作法。
爲何本文中的代碼在比較哈希值的時候,都是通過固定的時間才返回結果?
讓比較過程耗費固定的時間能夠保證攻擊者沒法對一個在線系統使用計時攻擊,以此獲取密碼的哈希值,而後進行本地破解工做。
比較兩個字節序列(字符串)的標準作法是,從第一字節開始,每一個字節逐一順序比較。只要發現某字節不相同了,就能夠當即返回「假」的結果。若是遍歷整個字符串也沒有找到不一樣的字節,那麼兩個字符串就是相同的,而且返回「真」。這意味着比較字符串的耗時決定於兩個字符串到底有多大的不一樣。
舉個例子,使用標準的方法比較「xyzabc」和「abcxyz」,因爲第一個字符就不一樣,不須要檢查後面的內容就能夠立刻返回結果。相反,若是比較「aaaaaaaaaaB」和「aaaaaaaaaaZ」,比較算法就須要遍歷最後一位前全部的「a」,而後才能知道它們是不相同的。
假設攻擊者妄圖入侵一個在線系統,而且此係統限制了每秒只能嘗試一次用戶認證。還假設他已經知道了密碼哈希全部的參數(鹽值、哈希函數的類型等等),除了密碼的哈希值和密碼自己(顯然啊,不然還破解個什麼)。若是攻擊者能精確測量在線系統耗時多久去比較他猜想的密碼和真實密碼,那麼他就能使用計時攻擊獲取密碼的哈希值,而後進行離線破解,從而繞過系統對認證頻率的限制。
首先攻擊者準備256個字符串,它們的哈希值的第一字節包含了全部可能的狀況。而後用它們去系統中嘗試登陸,並記錄系統返回結果所消耗的時間,耗時最長的那個就是第一字節猜對的那個。接下來用一樣的方式猜想第二字節、第三字節等等。直到攻擊者獲取了最夠長的哈希值片斷,最後只需在本身的機器上破解便可,徹底不受在線系統的限制。
乍看之下在網絡上進行計時攻擊是不可能作到的,然而有人已經實現了,並運用到實際中了。所以本文提供的代碼才使用固定的時間去比較字符串,不論它們有多類似。
「慢比較」的代碼是如何工做的?
上一個問題解釋了爲何「慢比較」是有必要的,如今來說解一下代碼具體是怎麼實現的。
|
private
static
boolean
slowEquals
(
byte
[
]
a
,
byte
[
]
b
)
{
int
diff
=
a
.
length
^
b
.
length
;
for
(
int
i
=
0
;
i
<
a
.
length
&&
i
<
b
.
length
;
i
++
)
diff
|=
a
[
i
]
^
b
[
i
]
;
return
diff
==
0
;
}
|
代碼中使用了異或運算符「^」(XOR)來比較兩個整數是否相等,而不是「==」。當且僅當兩位相等時,異或的結果纔是0。由於0 XOR 0 = 0, 1 XOR 1 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1。應用到整數中每一位就是說,當且僅當字節兩個整數各位都相等,結果纔是0。
代碼中的第一行,比較a.length和b.length,相同的話diff是0,不然diff非0。而後使用異或比較數組中各字節,而且將結果和diff求或。若是有任何一個字節不相同,diff就會變成非0的值。由於或運算沒有「置0」的功能,因此循環結束後diff是0的話只有一種可能,那就是循環前兩個數組長度相等(a.length == b.length),而且數組中每個字節都相同(每次異或的結果都非0)。
咱們使用XOR而不是「==」來比較整數的緣由是:「==」一般被翻譯/編譯/解釋爲帶有分支的語句。例如C語言中的「diff &= a == b」可能在x86機器成被編譯爲以下彙編語言:
MOV EAX, [A]
CMP [B], EAX
JZ equal
JMP done
equal:
AND [VALID], 1
done:
AND [VALID], 0
其中的分支致使代碼運行的時間不固定,決定於兩個整數相等的程度和CPU內部的跳轉預測機制(branch prediction)。
而C語言代碼「diff |=a ^ b」會被編譯爲下面的樣子,它執行的時間和兩個整數是什麼樣的狀況無關。
MOV EAX, [A]
XOR EAX, [B]
OR [DIFF], EAX
弄這麼麻煩幹嗎?
用戶在你的網站上輸入密碼,說明他們相信你會保障密碼的安全。若是你的數據庫被黑了,又沒有對用戶密碼加以保護,惡意黑客就能夠使用這些密碼去入侵用戶在其餘網站或服務的帳戶(大部分人會在各處使用相同的密碼)。這不只僅關乎你網站的安全,更關係到用戶的。你須要對用戶的安全負責。
PHP PBKDF2 密碼哈希代碼
下面是PBKDF2在PHP中一種安全的實現,你也能夠在這個頁面找到測試用例和基準測試的代碼。
下載PasswordHash.php
若是你須要兼容的PHP和C#代碼,點擊這裏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
<?php
/*
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// These constants may be changed without breaking existing hashes.
define
(
"PBKDF2_HASH_ALGORITHM"
,
"sha256"
)
;
define
(
"PBKDF2_ITERATIONS"
,
1000
)
;
define
(
"PBKDF2_SALT_BYTE_SIZE"
,
24
)
;
define
(
"PBKDF2_HASH_BYTE_SIZE"
,
24
)
;
define
(
"HASH_SECTIONS"
,
4
)
;
define
(
"HASH_ALGORITHM_INDEX"
,
0
)
;
define
(
"HASH_ITERATION_INDEX"
,
1
)
;
define
(
"HASH_SALT_INDEX"
,
2
)
;
define
(
"HASH_PBKDF2_INDEX"
,
3
)
;
function
create_hash
(
$password
)
{
// format: algorithm:iterations:salt:hash
$salt
=
base64_encode
(
mcrypt_create_iv
(
PBKDF2_SALT_BYTE_SIZE
,
MCRYPT_DEV_URANDOM
)
)
;
return
PBKDF2_HASH_ALGORITHM
.
":"
.
PBKDF2_ITERATIONS
.
":"
.
$salt
.
":"
.
base64_encode
(
pbkdf2
(
PBKDF2_HASH_ALGORITHM
,
$password
,
$salt
,
PBKDF2_ITERATIONS
,
PBKDF2_HASH_BYTE_SIZE
,
true
)
)
;
}
function
validate_password
(
$password
,
$correct_hash
)
{
$params
=
explode
(
":"
,
$correct_hash
)
;
if
(
count
(
$params
)
<
HASH_SECTIONS
)
return
false
;
$pbkdf2
=
base64_decode
(
$params
[
HASH_PBKDF2_INDEX
]
)
;
return
slow_equals
(
$pbkdf2
,
pbkdf2
(
$params
[
HASH_ALGORITHM_INDEX
]
,
$password
,
$params
[
HASH_SALT_INDEX
]
,
(
int
)
$params
[
HASH_ITERATION_INDEX
]
,
strlen
(
$pbkdf2
)
,
true
)
)
;
}
// Compares two strings $a and $b in length-constant time.
function
slow_equals
(
$a
,
$b
)
{
$diff
=
strlen
(
$a
)
^
strlen
(
$b
)
;
for
(
$i
=
0
;
$i
<
strlen
(
$a
)
&&
$i
<
strlen
(
$b
)
;
$i
++
)
{
$diff
|=
ord
(
$a
[
$i
]
)
^
ord
(
$b
[
$i
]
)
;
}
return
$diff
===
0
;
}
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function
pbkdf2
(
$algorithm
,
$password
,
$salt
,
$count
,
$key_length
,
$raw_output
=
false
)
{
$algorithm
=
strtolower
(
$algorithm
)
;
if
(
!
in_array
(
$algorithm
,
hash_algos
(
)
,
true
)
)
trigger_error
(
'PBKDF2 ERROR: Invalid hash algorithm.'
,
E_USER_ERROR
)
;
if
(
$count
<=
0
||
$key_length
<=
0
)
trigger_error
(
'PBKDF2 ERROR: Invalid parameters.'
,
E_USER_ERROR
)
;
if
(
function_exists
(
"hash_pbkdf2"
)
)
{
// The output length is in NIBBLES (4-bits) if $raw_output is false!
if
(
!
$raw_output
)
{
$key_length
=
$key_length
*
2
;
}
return
hash_pbkdf2
(
$algorithm
,
$password
,
$salt
,
$count
,
$key_length
,
$raw_output
)
;
}
$hash_length
=
strlen
(
hash
(
$algorithm
,
""
,
true
)
)
;
$block_count
=
ceil
(
$key_length
/
$hash_length
)
;
$output
=
""
;
for
(
$i
=
1
;
$i
<=
$block_count
;
$i
++
)
{
// $i encoded as 4 bytes, big endian.
$last
=
$salt
.
pack
(
"N"
,
$i
)
;
// first iteration
$last
=
$xorsum
=
hash_hmac
(
$algorithm
,
$last
,
$password
,
true
)
;
// perform the other $count - 1 iterations
for
(
$j
=
1
;
$j
<
$count
;
$j
++
)
{
$xorsum
^=
(
$last
=
hash_hmac
(
$algorithm
,
$last
,
$password
,
true
)
)
;
}
$output
.
=
$xorsum
;
}
if
(
$raw_output
)
return
substr
(
$output
,
0
,
$key_length
)
;
else
return
bin2hex
(
substr
(
$output
,
0
,
$key_length
)
)
;
}
?>
|
Java PBKDF2 密碼哈希代碼
下面是PBKDF2在Java中一種安全的實現。
下載PasswordHash.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
/**
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import
java
.
security
.
SecureRandom
;
import
javax
.
crypto
.
spec
.
PBEKeySpec
;
import
javax
.
crypto
.
SecretKeyFactory
;
import
java
.
math
.
BigInteger
;
import
java
.
security
.
NoSuchAlgorithmException
;
import
java
.
security
.
spec
.
InvalidKeySpecException
;
/**
* PBKDF2 salted password hashing.
* Author: havoc AT defuse.ca
* www: http://crackstation.net/hashing-security.htm
*/
public
class
PasswordHash
{
public
static
final
String
PBKDF2_ALGORITHM
=
"PBKDF2WithHmacSHA1"
;
// The following constants may be changed without breaking existing hashes.
public
static
final
int
SALT_BYTE_SIZE
=
24
;
public
static
final
int
HASH_BYTE_SIZE
=
24
;
public
static
final
int
PBKDF2_ITERATIONS
=
1000
;
public
static
final
int
ITERATION_INDEX
=
0
;
public
static
final
int
SALT_INDEX
=
1
;
public
static
final
int
PBKDF2_INDEX
=
2
;
/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password the password to hash
* @return a salted PBKDF2 hash of the password
*/
public
static
String
createHash
(
String
password
)
throws
NoSuchAlgorithmException
,
InvalidKeySpecException
{
return
createHash
(
password
.
toCharArray
(
)
)
;
}
/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password the password to hash
* @return a salted PBKDF2 hash of the password
*/
public
static
String
createHash
(
char
[
]
password
)
throws
NoSuchAlgorithmException
,
InvalidKeySpecException
{
// Generate a random salt
SecureRandom
random
=
new
SecureRandom
(
)
;
byte
[
]
salt
=
new
byte
[
SALT_BYTE_SIZE
]
;
random
.
nextBytes
(
salt
)
;
// Hash the password
byte
[
]
hash
=
pbkdf2
(
password
,
salt
,
PBKDF2_ITERATIONS
,
HASH_BYTE_SIZE
)
;
// format iterations:salt:hash
return
PBKDF2_ITERATIONS
+
":"
+
toHex
(
salt
)
+
":"
+
toHex
(
hash
)
;
}
/**
* Validates a password using a hash.
*
* @param password the password to check
* @param correctHash the hash of the valid password
* @return true if the password is correct, false if not
*/
public
static
boolean
validatePassword
(
String
password
,
String
correctHash
)
throws
NoSuchAlgorithmException
,
InvalidKeySpecException
{
return
validatePassword
(
password
.
toCharArray
(
)
,
correctHash
)
;
}
/**
* Validates a password using a hash.
*
* @param password the password to check
* @param correctHash the hash of the valid password
* @return true if the password is correct, false if not
*/
public
static
boolean
validatePassword
(
char
[
]
password
,
String
correctHash
)
throws
NoSuchAlgorithmException
,
InvalidKeySpecException
{
// Decode the hash into its parameters
String
[
]
params
=
correctHash
.
split
(
":"
)
;
int
iterations
=
Integer
.
parseInt
(
params
[
ITERATION_INDEX
]
)
;
byte
[
]
salt
=
fromHex
(
params
[
SALT_INDEX
]
)
;
byte
[
]
hash
=
fromHex
(
params
[
PBKDF2_INDEX
]
)
;
// Compute the hash of the provided password, using the same salt,
// iteration count, and hash length
byte
[
]
testHash
=
pbkdf2
(
password
,
salt
,
iterations
,
hash
.
length
)
;
// Compare the hashes in constant time. The password is correct if
// both hashes match.
return
slowEquals
(
hash
,
testHash
)
;
}
/**
* Compares two byte arrays in length-constant time. This comparison method
* is used so that password hashes cannot be extracted from an on-line
* system using a timing attack and then attacked off-line.
*
* @param a the first byte array
* @param b the second byte array
* @return true if both byte arrays are the same, false if not
*/
private
static
boolean
slowEquals
(
byte
[
]
a
,
byte
[
]
b
)
{
int
diff
=
a
.
length
^
b
.
length
;
for
(
int
i
=
0
;
i
<
a
.
length
&&
i
<
b
.
length
;
i
++
)
diff
|=
a
[
i
]
^
b
[
i
]
;
return
diff
==
0
;
}
/**
* Computes the PBKDF2 hash of a password.
*
* @param password the password to hash.
* @param salt the salt
* @param iterations the iteration count (slowness factor)
* @param bytes the length of the hash to compute in bytes
* @return the PBDKF2 hash of the password
*/
private
static
byte
[
]
pbkdf2
(
char
[
]
password
,
byte
[
]
salt
,
int
iterations
,
int
bytes
)
throws
NoSuchAlgorithmException
,
InvalidKeySpecException
{
PBEKeySpec
spec
=
new
PBEKeySpec
(
password
,
salt
,
iterations
,
bytes
*
8
)
;
SecretKeyFactory
skf
=
SecretKeyFactory
.
getInstance
(
PBKDF2_ALGORITHM
)
;
return
skf
.
generateSecret
(
spec
)
.
getEncoded
(
)
;
}
/**
* Converts a string of hexadecimal characters into a byte array.
*
* @param hex the hex string
* @return the hex string decoded into a byte array
*/
private
static
byte
[
]
fromHex
(
String
hex
)
{
byte
[
]
binary
=
new
byte
[
hex
.
length
(
)
/
2
]
;
for
(
int
i
=
0
;
i
<
binary
.
length
;
i
++
)
{
binary
[
i
]
=
(
byte
)
Integer
.
parseInt
(
hex
.
substring
(
2
*
i
,
2
*
i
+
2
)
,
16
)
;
}
return
binary
;
}
/**
* Converts a byte array into a hexadecimal string.
*
* @param array the byte array to convert
* @return a length*2 character string encoding the byte array
*/
private
static
String
toHex
(
byte
[
]
array
)
{
BigInteger
bi
=
new
BigInteger
(
1
,
array
)
;
String
hex
=
bi
.
toString
(
16
)
;
int
paddingLength
=
(
array
.
length
*
2
)
-
hex
.
length
(
)
;
if
(
paddingLength
>
0
)
return
String
.
format
(
"%0"
+
paddingLength
+
"d"
,
0
)
+
hex
;
else
return
hex
;
}
/**
* Tests the basic functionality of the PasswordHash class
*
* @param args ignored
*/
public
static
void
main
(
String
[
]
args
)
{
try
{
// Print out 10 hashes
for
(
int
i
=
0
;
i
<
10
;
i
++
)
System
.
out
.
println
(
PasswordHash
.
createHash
(
"p\r\nassw0Rd!"
)
)
;
// Test password validation
boolean
failure
=
false
;
System
.
out
.
println
(
"Running tests..."
)
;
for
(
int
i
=
0
;
i
<
100
;
i
++
)
{
String
password
=
""
+
i
;
String
hash
=
createHash
(
password
)
;
String
secondHash
=
createHash
(
password
)
;
if
(
hash
.
equals
(
secondHash
)
)
{
System
.
out
.
println
(
"FAILURE: TWO HASHES ARE EQUAL!"
)
;
failure
=
true
;
}
String
wrongPassword
=
""
+
(
i
+
1
)
;
if
(
validatePassword
(
wrongPassword
,
hash
)
)
{
System
.
out
.
println
(
"FAILURE: WRONG PASSWORD ACCEPTED!"
)
;
failure
=
true
;
}
if
(
!
validatePassword
(
password
,
hash
)
)
{
System
.
out
.
println
(
"FAILURE: GOOD PASSWORD NOT ACCEPTED!"
)
;
failure
=
true
;
}
}
if
(
failure
)
System
.
out
.
println
(
"TESTS FAILED!"
)
;
else
System
.
out
.
println
(
"TESTS PASSED!"
)
;
}
catch
(
Exception
ex
)
{
System
.
out
.
println
(
"ERROR: "
+
ex
)
;
}
}
}
|
ASP.NET(C#) PBKDF2 密碼哈希代碼
下面是PBKDF2在ASP.NET(C#)中一種安全的實現。
下載PasswordHash.cs
若是你須要兼容的PHP和C#代碼,點擊這裏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
/*
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using
System
;
using
System
.
Text
;
using
System
.
Security
.
Cryptography
;
namespace
PasswordHash
{
/// <summary>
/// Salted password hashing with PBKDF2-SHA1.
/// Author: havoc AT defuse.ca
/// www: http://crackstation.net/hashing-security.htm
/// Compatibility: .NET 3.0 and later.
/// </summary>
public
class
PasswordHash
{
// The following constants may be changed without breaking existing hashes.
public
const
int
SALT_BYTE_SIZE
=
24
;
public
const
int
HASH_BYTE_SIZE
=
24
;
public
const
int
PBKDF2_ITERATIONS
=
1000
;
public
const
int
ITERATION_INDEX
=
0
;
public
const
int
SALT_INDEX
=
1
;
public
const
int
PBKDF2_INDEX
=
2
;
/// <summary>
/// Creates a salted PBKDF2 hash of the password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <returns>The hash of the password.</returns>
public
static
string
CreateHash
(
string
password
)
{
// Generate a random salt
RNGCryptoServiceProvider
csprng
=
new
RNGCryptoServiceProvider
(
)
;
byte
[
]
salt
=
new
byte
[
SALT_BYTE_SIZE
]
;
csprng
.
GetBytes
(
salt
)
;
// Hash the password and encode the parameters
byte
[
]
hash
=
PBKDF2
(
password
,
salt
,
PBKDF2_ITERATIONS
,
HASH_BYTE_SIZE
)
;
return
PBKDF2_ITERATIONS
+
":"
+
Convert
.
ToBase64String
(
salt
)
+
":"
+
Convert
.
ToBase64String
(
hash
)
;
}
/// <summary>
/// Validates a password given a hash of the correct one.
/// </summary>
/// <param name="password">The password to check.</param>
/// <param name="correctHash">A hash of the correct password.</param>
/// <returns>True if the password is correct. False otherwise.</returns>
public
static
bool
ValidatePassword
(
string
password
,
string
correctHash
)
{
// Extract the parameters from the hash
char
[
]
delimiter
=
{
':'
}
;
string
[
]
split
=
correctHash
.
Split
(
delimiter
)
;
int
iterations
=
Int32
.
Parse
(
split
[
ITERATION_INDEX
]
)
;
byte
[
]
salt
=
Convert
.
FromBase64String
(
split
[
SALT_INDEX
]
)
;
byte
[
]
hash
=
Convert
.
FromBase64String
(
split
[
PBKDF2_INDEX
]
)
;
byte
[
]
testHash
=
PBKDF2
(
password
,
salt
,
iterations
,
hash
.
Length
)
;
return
SlowEquals
(
hash
,
testHash
)
;
}
/// <summary>
/// Compares two byte arrays in length-constant time. This comparison
/// method is used so that password hashes cannot be extracted from
/// on-line systems using a timing attack and then attacked off-line.
/// </summary>
/// <param name="a">The first byte array.</param>
/// <param name="b">The second byte array.</param>
/// <returns>True if both byte arrays are equal. False otherwise.</returns>
private
static
bool
SlowEquals
(
byte
[
]
a
,
byte
[
]
b
)
{
uint
diff
=
(
uint
)
a
.
Length
^
(
uint
)
b
.
Length
;
for
(
int
i
=
0
;
i
<
a
.
Length
&&
i
<
b
.
Length
;
i
++
)
diff
|=
(
uint
)
(
a
[
i
]
^
b
[
i
]
)
;
return
diff
==
0
;
}
/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterations">The PBKDF2 iteration count.</param>
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private
static
byte
[
]
PBKDF2
(
string
password
,
byte
[
]
salt
,
int
iterations
,
int
outputBytes
)
{
Rfc2898DeriveBytes
pbkdf2
=
new
Rfc2898DeriveBytes
(
password
,
salt
)
;
pbkdf2
.
IterationCount
=
iterations
;
return
pbkdf2
.
GetBytes
(
outputBytes
)
;
}
}
}
|
Ruby(on Rails) PBKDF2 密碼哈希代碼
下面是PBKDF2在Ruby(on Rails)中一種安全的實現。
下載PasswordHash.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|