MD5算法实现原理
目錄
- 1 MD5 簡介
- 2 MD5 實現步驟
- 2.1 將明文變為 ASCII 碼
- 2.2 填充和附加
- 2.3 分組
- 2.4 預處理
- 2.5 循環加工
- 3 blueimp-md5 源碼
- 4 遺留的問題
- 4.1 四個非線性函數是如何設計的
- 4.2 循環左移偏移量是如何設計的
- 4.3 正弦補充值是如何設計的
- 5 總結
1 MD5 簡介
信息摘要算法 MD5 (Message Digest 5),數字 5 代表這是第五代算法,之前還有 MD1、MD2、MD3 和 MD4。MD5 是一種被廣泛使用的密碼散列函數,可以產生出一個 128128128 位(161616 字節)的散列值(hash value),用于確保信息傳輸完整一致。
MD5 算法的特征是不可逆性,并且在計算的時候所有的數據都參與了運算,其中任何一個數據變化了都會導致計算出來的 Hash 值完全不同,所以通常用來校驗數據是否正確或用作身份驗證。
2 MD5 實現步驟
2.1 將明文變為 ASCII 碼
要加密的明文:China,my great country。
ASCII碼:43 68 69 6E 61 2C 6D 79 20 67 72 65 61 74 20 63 6F 75 6E 74 72 79
2.2 填充和附加
對數據進行填充的目的是使任意長度的數據能夠被 512512512 整除,填充的首位是 111,其余位全部為 000,填充后數據變成 (N?512?64)(N*512-64)(N?512?64) 位(下圖中假設 N = 1,則填充后要達到 448448448 位)。
減去 646464 是因為要在數據末尾附加長度項,如果數據的長度大于 646464 位,則只取低 646464 位。通過填充和附加后,整個數據變為 512512512 的整數倍。
下面對數據進行填充和附加操作,數據為4368696E612C6D7920677265617420636F756E747279,這段數據長度為 176176176 位(注意上述數據是用 161616 進制表示的),由于 176<448176 < 448176<448,所以需要進行填充,共需填充 448?176=272448-176=272448?176=272 位,用 161616 進制表示為 80000....0000(272272272位),再加上數據長度項 176176176 的 161616 進制為 00B0,但是要湊成 646464 位,所以最后附加 B000000000000000。
填充和附加后的數據為:4368696E612C6D7920677265617420636F756E747279 80000....0000(272272272位)B000000000000000。總長度剛好為 512512512 位。
2.3 分組
將數據按每組 512512512 位進行分組,每組內再分成 161616 組,每組 323232 位。
上述數據按要求分組后如下表,并把每組數據保存在數組 x 中,表中的 # 代表在數組中的下標:
| 0 | 6E696843 |
| 1 | 796D2C61 |
| 2 | 65726720 |
| 3 | 63207461 |
| 4 | 746E756F |
| … | … |
| 11 | 00000000 |
| 12 | 00000000 |
| 13 | 00000000 |
| 14 | 000000B0 |
| 15 | 00000000 |
2.4 預處理
首先要確定 444 個鏈接變量 A、B、C、D,每個變量 323232 位。
| A | 0x67452301 |
| B | 0xEFCDAB89 |
| C | 0x98BADCFE |
| D | 0x10325476 |
這四個變量看似毫無章法,其實是有規律的,由于 Windows 是基于 Little Endian 的,即低位字節排放在內存的低地址端,高位字節排放在內存的高地址端,所以我們在定義這四個變量的值的時候要按它們在內存中的存儲形式進行定義,實際上這四個變量的邏輯形式是:
| A | 0x01234567 |
| B | 0x89ABCDEF |
| C | 0xFEDCBA98 |
| D | 0x76543210 |
仔細觀察后,規律就很明顯了。
2.5 循環加工
當第一輪的第一步開始處理時,將這 444 個變量先分別賦值到另外 444 個變量 oldA、oldB、oldC、oldD 中,以第一輪第一步為例,對應第一輪第一步的操作函數為 md5ff(a, b, c, d, x[0], 7, 0xD76AA478)(7 用于循環左移,0xD76AA478 為正弦補充值),首先將 b、c、d 經過非線性函數 (b&c) | (not(b)&d) 計算得到98BADCFE后,再將這個值加上 A、x[0] 和 0xD76AA478,即 67452301 + 98BADCFE + 6E696843 + D76AA478 = 45D40CBA。
再將相加結果 45D40CBA 循環左移七位得 EA065D22,再加上鏈接變量 B 的值,即 EA065D22 + EFCDAB89 = D9D408AB,最后把這個值賦值給鏈接變量 A。
依次類推,經過 444 輪操作,共 646464 個步驟后, 444 個鏈接變量變為:
| A | 0xD32A3F0D |
| B | 0x0E63C780 |
| C | 0x469E5215 |
| D | 0x65C53CCA |
接下來要將原 A、B、C、D 的值和上表中處理后的A、B、C、D 值相加求和,將和分別賦值給A、B、C、D,這個時候就用到了之前的 oldA、oldB、oldC、oldD ,具體操作以 A 為例,將 A 與 oldA 相加,即 D32A3F0D + 67452301 = 3A6F620E,再將和賦值給 A。全部求和后結果如下表:
| A | 0x3A6F620E |
| B | 0xFE317309 |
| C | 0xDF592F13 |
| D | 0x75F79140 |
最后算得的散列值為:0E626F3A097331FE132F59DF4091F775。
3 blueimp-md5 源碼
blueimp-md5 是由 blueimp 實現的 JavaScript MD5 算法。閱讀源碼后發現他們用了很多位運算來實現整個過程,有的地方生澀難懂。源碼中一共提供了 444 種 API:
最常用的還是第一個,下面列出 blueimp-md5 源碼實現過程,只截取了關鍵部分。
將字符串編碼為 UTF-8。
/** Encode a string as utf-8*/function str2rstrUTF8 (input) {return unescape(encodeURIComponent(input))}將原始字符串轉換為 161616 進制。
/** Convert a raw string to a hex string*/function rstr2hex (input) {var hexTab = '0123456789abcdef'var output = ''var xvar ifor (i = 0; i < input.length; i += 1) {x = input.charCodeAt(i)output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)}return output}四個非線性函數。
/** These functions implement the four basic operations the algorithm uses.*/function md5cmn (q, a, b, x, s, t) {return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)}function md5ff (a, b, c, d, x, s, t) {return md5cmn((b & c) | (~b & d), a, b, x, s, t)}function md5gg (a, b, c, d, x, s, t) {return md5cmn((b & d) | (c & ~d), a, b, x, s, t)}function md5hh (a, b, c, d, x, s, t) {return md5cmn(b ^ c ^ d, a, b, x, s, t)}function md5ii (a, b, c, d, x, s, t) {return md5cmn(c ^ (b | ~d), a, b, x, s, t)}循環左移。
/** Bitwise rotate a 32-bit number to the left.*/function bitRotateLeft (num, cnt) {return (num << cnt) | (num >>> (32 - cnt))}求和,生成新的鏈接變量。
/** Add integers, wrapping at 2^32. This uses 16-bit operations internally* to work around bugs in some JS interpreters.*/function safeAdd (x, y) {var lsw = (x & 0xffff) + (y & 0xffff)var msw = (x >> 16) + (y >> 16) + (lsw >> 16)return (msw << 16) | (lsw & 0xffff)}計算 MD5 哈希值,注意該函數中的 a、b、c、d 和 非線性函數中的數(正弦補充值)都是以十進制表示的。
/** Calculate the MD5 of an array of little-endian words, and a bit length.*/function binlMD5 (x, len) {/* append padding */x[len >> 5] |= 0x80 << (len % 32)x[((len + 64) >>> 9 << 4) + 14] = lenvar ivar oldavar oldbvar oldcvar olddvar a = 1732584193var b = -271733879var c = -1732584194var d = 271733878for (i = 0; i < x.length; i += 16) {olda = aoldb = boldc = coldd = da = md5ff(a, b, c, d, x[i], 7, -680876936)d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)c = md5ff(c, d, a, b, x[i + 10], 17, -42063)b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)b = md5gg(b, c, d, a, x[i], 20, -373897302)a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)a = md5hh(a, b, c, d, x[i + 5], 4, -378558)d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)d = md5hh(d, a, b, c, x[i], 11, -358537222)c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)a = md5ii(a, b, c, d, x[i], 6, -198630844)d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)a = safeAdd(a, olda)b = safeAdd(b, oldb)c = safeAdd(c, oldc)d = safeAdd(d, oldd)}return [a, b, c, d]}4 遺留的問題
4.1 四個非線性函數是如何設計的
在循環加工中,(b&c) | (not(b)&d) = 98BADCFE,即為一次非線性函數計算。
| 1 | F(X,Y,Z) = (X & Y) | ((~X) & Z); |
| 2 | G(X,Y,Z) = (X & Z) | (Y & (~Z)); |
| 3 | H(X,Y,Z) = X ^ Y ^ Z; |
| 4 | I(X,Y,Z) = Y ^ (X | (~Z)); |
這四個非線性函數分別被封裝到四個操作函數中,即循環加工中的 md5ff 函數就為操作函數之一,還有 md5gg、md5hh 和 md5ii。
4.2 循環左移偏移量是如何設計的
四個操作函數中,都附帶了參數作為循環左移偏移量,閱讀代碼后,發現循環左移偏移量分為如下四組:
| 1 | 7, 12, 17, 22 |
| 2 | 5, 9, 14, 20 |
| 3 | 4, 11, 16, 23 |
| 4 | 6, 10, 15, 21 |
4.3 正弦補充值是如何設計的
四個操作函數中,最后一個參數為正弦補充值,下面是十進制表示:
a = md5ff(a, b, c, d, x[i], 7, -680876936)d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)...轉換為十六進制表示為:
a = md5ff(a, b, c, d, x[i], 7, 0xD76AA478)d = md5ff(d, a, b, c, x[i + 1], 12, 0xE8C7B756)...5 總結
目前只對 MD5 算法有個初步的了解,而上面遺留的問題正是 MD5 算法的核心實現部分,不禁讓人感嘆算法設計者的腦洞。
從個人目前的認知來看,算法設計的數據和計算函數,目的只有一個,就是想方設法的打亂數據并直接讓每一位都參與進來,這也就是為什么任何一位的變化,都會影響最后的哈希值,如明文:Hello syzdev的哈希值為:704558b48d6370fb71533d5ae50727b4,在改動其中一個字母后明文變為 Hallo syzdev ,其產生的哈希值為:2433084e178a2331d40c2ea16fd664fa,這兩個哈希值看起來找不出有任何關聯,即使兩者的明文僅有一個字母差別。
MD5 將整個數據拆分成多個組,再由每個組內分別計算累加,有些資料將分組過程分為 333 步,即先將整個數據按 512512512 位分塊,再把每個 512512512 位分成 444 個 128128128 位數據塊,最后將 444 個 128128128 位的數據塊依次送到不同的散列函數進行 444 輪計算。每一輪又都按 323232 位的小數據塊進行復雜的運算,一直到最后計算出 MD5 報文摘要代碼,這也就是為什么 MD5 能把不定長的數據最后都變成 128128128 位的哈希值。
可以發現,MD5 的處理函數,包括非線性函數和操作函數都包含了多個參數,而且還是變動的參數,這也就保證了 MD5 是個不可逆的算法。舉個簡單的例子,如有函數 z=f(x)=5?xz=f(x)=5*xz=f(x)=5?x,此時知道 z=10z=10z=10,那么顯然得到 x=2x=2x=2;如函數為 z=f(x,y)=x?yz=f(x, y)=x*yz=f(x,y)=x?y,此時知道 z=10z=10z=10,這種情況下是無法推斷出 xxx 和 yyy 的值的,這樣也就保證了 MD5 算法具有不可逆性。
正是因為 MD5 算法具有不可逆性,所以目前存在的破解算法都不是恢復明文,而是只要能找到能生成此摘要的其中一個原文即可,這種破解方法叫做“碰撞”。如 H(A)=MH(A) = MH(A)=M,H(B)=MH(B) = MH(B)=M,則稱 BBB 為破解結果,此時 AAA 和 BBB 可能相等,也可能不等。
總結
- 上一篇: 操作系统原理第十一章:大容量存储
- 下一篇: 解决前后端base64编码传递时的中文乱