MD5檢驗算法的原理及實現
MD5消息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致。MD5由美國密碼學家羅納德·李維斯特(Ronald Linn Rivest)設計,於1992年公開,用以取代MD4算法。這套算法的程序在 RFC 1321 中被加以規範。
將數據(如一段文字)運算變爲另一固定長度值,是散列算法的基礎原理。
1996年後被證實存在弱點,可以被加以破解,對於需要高度安全性的數據,專家一般建議改用其他算法,如SHA-2。2004年,證實MD5算法無法防止碰撞(collision),因此不適用於安全性認證,如SSL公開密鑰認證或是數字簽名等用途, 當前,由於MD5算法因其普遍、穩定、快速的特點,仍廣泛應用於普通數據的錯誤檢查、驗證領域。
以對用戶密碼驗證爲例,當前大部分服務端程序保存用戶密碼的md5值,而不是原密碼,服務端程序在驗證用戶的輸入密碼時,先將用戶輸入字節進行md5換算再與數據庫中保存的md5值進行驗證.
MD5散列函數的特點是:即使修改原數據的一個微小字節也會導致最終計算出的散列值發生巨大的變化。
算法
我們首先假設有一個 b 個bit的數據作爲輸入,然後我們希望根據某種規則計算出它的信息摘要。這裏b是任意大小的數據,也不需要是8bit的倍數。 我們假設數據的字節如下:
m_0 m_1 … m_{b-1}
後續的5個步驟是計算出該數據的散列值的步驟
步驟1 填充位
輸入數據首先被"填充"(拓展),以保證 它的長度%512 = 448。填充總是被執行,即使輸入數據的長度已經滿足%523=448。
填充的具體方式如下:
在輸入字節數據後追加一個位"1",然後追加位"0"使長度%512=448。總之,至少一個位(位1)、至多512位被追加到源數據後
步驟2 追加消息長度信息
追加一個64位的數據,該數據表示b的長度(步驟1填充之後的),如果b的長度大於2^64,那麼只使用該長度值二進制表示的低64位(比如如果長度是0x82a89c7434,則只使用0xa89c7434)。
追加長度信息後,此時數據的長度是 512的倍數,並且也是16的倍數。
步驟3 初始化 MD Buffer
創建一個4-world 大小的 buffer (A、B、C、D)用於計算消息摘要。這裏的A、B、C、D都是32位寄存器。這些寄存器初始化爲以下十六進制低階字節的值
word A: 01 23 45 67
word B: 89 ab cd ef
word C: fe dc ba 98
word D: 76 54 32 10
在僞代程序中表示爲
var int h0 := 0x67452301
var int h1 := 0xEFCDAB89
var int h2 := 0x98BADCFE
var int h3 := 0x10325476
步驟4 以 16-World Blocks 處理消息
我們首先定義四個輔助函數,每個函數輸入三個32位的單詞,輸出一個32位的單詞。
**F(X,Y,Z) **= ( **X **& **Y **) | ( ( ~**X **) & **Z **)
G(X,Y,Z) = ( X & Z ) | ( Y & ( ~Z ) )
**H(X,Y,Z) **= XYZ
**I(X,Y,Z) **= Y^( X | ( ~Z ) )
定義 一個包含64個元素的表 r[1 … 64] ,該表每個位置 i 表示 第i輪使用 leftRotate的位數 (left rotate 即從高位移入低位)
定義一個包含64個元素的表 K[1 … 64],該表由正弦函數的值組合合成,每一個位置 i 的值爲 floor(abs(sin(i + 1)) × 2^32)。
處理流程的僞代碼如下:
**
//Note: All variables are unsigned 32 bits and wrap modulo 2^32 when calculating
var int[64] r, k
//r specifies the per-round shift amounts
r[ 0..15]:= {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22}
r[16..31]:= {5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20}
r[32..47]:= {4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23}
r[48..63]:= {6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}
//Use binary integer part of the sines of integers as constants:
for i from 0 to 63
k[i] := floor(abs(sin(i + 1)) × 2^32)
//Initialize variables:
var int h0 := 0x67452301
var int h1 := 0xEFCDAB89
var int h2 := 0x98BADCFE
var int h3 := 0x10325476
//Pre-processing:
append "1" bit to message
append "0" bits until message length in bits ≡ 448 (mod 512)
append bit length of message as 64-bit little-endian integer to message
//Process the message in successive 512-bit chunks:
for each 512-bit chunk of message
break chunk into sixteen 32-bit little-endian words w[i], 0 ≤ i ≤ 15
//Initialize hash value for this chunk:
var int a := h0
var int b := h1
var int c := h2
var int d := h3
//Main loop:
for i from 0 to 63
if 0 ≤ i ≤ 15 then
f := (b and c) or ((not b) and d)
g := i
else if 16 ≤ i ≤ 31
f := (d and b) or ((not d) and c)
g := (5×i + 1) mod 16
else if 32 ≤ i ≤ 47
f := b xor c xor d
g := (3×i + 5) mod 16
else if 48 ≤ i ≤ 63
f := c xor (b or (not d))
g := (7×i) mod 16
temp := d
d := c
c := b
b := leftrotate((a + f + k[i] + w[g]),r[i]) + b
a := temp
Next i
//Add this chunk's hash to result so far:
h0 := h0 + a
h1 := h1 + b
h2 := h2 + c
h3 := h3 + d
End ForEach
var int digest := h0 append h1 append h2 append h3 //(expressed as little-endian)
上述算法流程圖如下:
Figure 1. 一個MD5運算— 由類似的64次循環構成,分成4組16次。F 一個非線性函數;一個函數運算一次。M 表示一個 32-bits 的輸入數據,K 表示一個 32-bits 常數,用來完成每次不同的計算。
更直觀計算過程是 以64byte(512bit)爲chunk執行以下函數過程
其中。
最後將緩存變量的拷貝和計算結果相加:
接着繼續對下一個 64bit進行計算,直到計算完所有的信息。
步驟5 輸出結果
最終散列函數的結果由最終的A ,B ,C ,D 組成,拼接這4個變量,其中其中A爲最低位,D爲最高位。
md5 hash破解
我們知道使用md5算法不論源數據多大最終計算的結果都是256位的,理論上得到一個md5值是無法還原得到它的源數據的。但是事實上我們還是有方法在數據有限域內破解md5的。舉個簡單的攻擊的例子,由於md5算法都是公開的,我們可以根據網上的一些弱口令、或者提前隨機生成很多字符串,算出這些字符串的md5值,那麼我們就形成了一個 {md5值:源字符串}的表,如果得到的md5值的源數據剛好落在這個表中,那麼我們就得到這個hash值可能的源數據。
除了預先計算md5,還有一種使用 彩虹表的方式進行碰撞攻擊,具體參見 wiki。這裏有一些免費的彩虹表數據。
hash 碰撞
我們知道hash函數是將任意數量的數據映射成一個固定長度的字符串,所以一定存在不同的輸入經過hash之後變成相同的字符串的情況。加密hash函數(Cryptographic hash function)在設計的時候希望使這種碰撞攻擊實現起來成本難以置信的高。但時不時的就有密碼學家發現快速實現hash碰撞的方法。最近的一個例子就是MD5,它的碰撞攻擊已經實現了。
碰撞攻擊是找到另外一個跟原密碼不一樣,但是具有相同hash的字符串。但是,即使在相對弱的hash算法,比如MD5,要實現碰撞攻擊也需要大量的算力(computing power),所以在實際使用中偶然出現hash碰撞的情況幾乎不太可能。一個使用加鹽MD5的密碼hash在實際使用中跟使用其他算法比如SHA256一樣安全。不過如果可以的話,使用更安全的hash函數,比如SHA256, SHA512, RipeMD, WHIRLPOOL等是更好的選擇。
哈希碰撞的概率取決於2個元素:
- 取值空間的大小(即哈希值的長度)
- 整個生命週期中,哈希值的計算次數
加鹽
因此,在一些敏感的數據保存,比如用戶密碼,最簡單的提升加密強度的方法就是“加鹽”,即在對用戶密碼進行md5運算前 額外添加上一個隨機字符串,再進行hash運算。
鹽的長度不能太短,如果太短攻擊者也可以提前針對可能的鹽製作查詢表。
參考
https://en.wikipedia.org/wiki/MD5#Simple_Implementation
加鹽hash保存密碼的正確方式
哈希碰撞