1. 程式人生 > >md5加密原理 MD5簡介

md5加密原理 MD5簡介

MD5簡介


MD5的全稱是Message-Digest Algorithm 5,在90年代初由MIT的電腦科學實驗室和RSA Data Security Inc發明,經MD2、MD3和MD4發展而來。

Message-Digest泛指位元組串(Message)的Hash變換,就是把一個任意長度的位元組串變換成一定長的大整數。請注意我使用了“位元組串”而不是“字串”這個詞,是因為這種變換隻與位元組的值有關,與字符集或編碼方式無關。

MD5將任意長度的“位元組串”變換成一個128bit的大整數,並且它是一個不可逆的字串變換演算法,換句話說就是,即使你看到源程式和演算法描述,也無法 將一個MD5的值變換回原始的字串,從數學原理上說,是因為原始的字串有無窮多個,這有點象不存在反函式的數學函式。

MD5的典型應用是對一段Message(位元組串)產生fingerprint(指紋),以防止被“篡改”。舉個例子,你將一段話寫在一個叫 readme.txt檔案中,並對這個readme.txt產生一個MD5的值並記錄在案,然後你可以傳播這個檔案給別人,別人如果修改了檔案中的任何內 容,你對這個檔案重新計算MD5時就會發現。如果再有一個第三方的認證機構,用MD5還可以防止檔案作者的“抵賴”,這就是所謂的數字簽名應用。

MD5還廣泛用於加密和解密技術上,在很多作業系統中,使用者的密碼是以MD5值(或類似的其它演算法)的方式儲存的,使用者Login的時候,系統是把使用者輸入的密碼計算成MD5值,然後再去和系統中儲存的MD5值進行比較,而系統並不“知道”使用者的密碼是什麼。

一些黑客破獲這種密碼的方法是一種被稱為“跑字典”的方法。有兩種方法得到字典,一種是日常蒐集的用做密碼的字串表,另一種是用排列組合方法生成的,先用MD5程式計算出這些字典項的MD5值,然後再用目標的MD5值在這個字典中檢索。

即使假設密碼的最大長度為8,同時密碼只能是字母和數字,共26+26+10=62個字元,排列組合出的字典的項數則是 P(62,1)+P(62,2)….+P(62,8),那也已經是一個很天文的數字了,儲存這個字典就需要TB級的磁碟組,而且這種方法還有一個前提,就 是能獲得目標賬戶的密碼MD5值的情況下才可以。

在很多電子商務和社群應用中,管理使用者的Account是一種最常用的基本功能,儘管很多Application Server提供了這些基本元件,但很多應用開發者為了管理的更大的靈活性還是喜歡採用關係資料庫來管理使用者,懶惰的做法是使用者的密碼往往使用明文或簡單 的變換後直接儲存在資料庫中,因此這些使用者的密碼對軟體開發者或系統管理員來說可以說毫無保密可言,本文的目的是介紹MD5的Java Bean的實現,同時給出用MD5來處理使用者的Account密碼的例子,這種方法使得管理員和程式設計者都無法看到使用者的密碼,儘管他們可以初始化它 們。但重要的一點是對於使用者密碼設定習慣的保護。

有興趣的讀者可以從這裡取得MD5也就是RFC 1321的文字。

http://www.ietf.org/rfc/rfc1321.txt

實現策略


MD5的演算法在RFC1321中實際上已經提供了C的實現,我們其實馬上就能想到,至少有兩種用Java實現它的方法,第一種是,用Java語言重新寫整 個演算法,或者再說簡單點就是把C程式改寫成Java程式。第二種是,用JNI(Java Native Interface)來實現,核心演算法仍然用這個C程式,用Java類給它包個殼。

但我個人認為,JNI應該是Java為了解決某類問題時的沒有辦法的辦法(比如與作業系統或I/O裝置密切相關的應用),同時為了提供和其它語言的互操作 性的一個手段。使用JNI帶來的最大問題是引入了平臺的依賴性,打破了SUN所鼓吹的“一次編寫到處執行”的Java好處。因此,我決定採取第一種方法, 一來和大家一起嘗試一下“一次編寫到處執行”的好處,二來檢驗一下Java 2現在對於比較密集的計算的效率問題。

實現過程


限於這篇文章的篇幅,同時也為了更多的讀者能夠真正專注於問題本身,我不想就某一種Java整合開發環境來介紹這個Java Bean的製作過程,介紹一個方法時我發現步驟和命令很清晰,我相信有任何一種Java整合環境三天以上經驗的讀者都會知道如何把這些程式碼在整合環境中編 譯和執行。用整合環境講述問題往往需要配很多螢幕截圖,這也是我一直對整合環境很頭疼的原因。我使用了一個普通的文字編輯器,同時使用了Sun公司標準的 JDK 1.3.0 for Windows NT。

其實把C轉換成Java對於一個有一定C語言基礎的程式設計師並不困難,這兩個語言的基本語法幾乎完全一致.我大概花了一個小時的時間完成了程式碼的轉換工作,我主要作了下面幾件事:

把必須使用的一些#define的巨集定義變成Class中的final static,這樣保證在一個程序空間中的多個Instance共享這些資料
刪去了一些無用的#if define,因為我只關心MD5,這個推薦的C實現同時實現了MD2 MD3和 MD4,而且有些#if define還和C不同編譯器有關
將一些計算巨集轉換成final static 成員函式。
所有的變數命名與原來C實現中保持一致,在大小寫上作一些符合Java習慣的變化,計算過程中的C函式變成了private方法(成員函式)。
關鍵變數的位長調整
定義了類和方法
需要注意的是,很多早期的C編譯器的int型別是16 bit的,MD5使用了unsigned long int,並認為它是32bit的無符號整數。而在Java中int是32 bit的,long是64 bit的。在MD5的C實現中,使用了大量的位操作。這裡需要指出的一點是,儘管Java提供了位操作,由於Java沒有unsigned型別,對於右移 位操作多提供了一個無符號右移:>>>,等價於C中的 >> 對於unsigned 數的處理。

因為Java不提供無符號數的運算,兩個大int數相加就會溢位得到一個負數或異常,因此我將一些關鍵變數在Java中改成了long型別 (64bit)。我個人認為這比自己去重新定義一組無符號數的類同時過載那些運算子要方便,同時效率高很多並且程式碼也易讀,OO(Object Oriented)的濫用反而會導致效率低下。

限於篇幅,這裡不再給出原始的C程式碼,有興趣對照的讀者朋友可以去看RFC 1321。MD5.java原始碼

測試


在RFC 1321中,給出了Test suite用來檢驗你的實現是否正確:

MD5 ("") = d41d8cd98f00b204e9800998ecf8427e

MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661

MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72

MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0

MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b

……

這些輸出結果的含義是指:空字串””的MD5值是d41d8cd98f00b204e9800998ecf8427e,字串”a”的MD5值是0cc175b9c0f1b6a831c399e269772661……
編譯並執行我們的程式:
javac –d . MD5.java
java beartool.MD5
為了將來不與別人的同名程式衝突,我在我的程式的第一行使用了package beartool;

因此編譯命令javac –d . MD5.java 命令在我們的工作目錄下自動建立了一個beartool目錄,目錄下放著編譯成功的 MD5.class

我們將得到和Test suite同樣的結果。當然還可以繼續測試你感興趣的其它MD5變換,例如:

java beartool.MD5 1234

將給出1234的MD5值。

可能是我的計算機知識是從Apple II和Z80單板機開始的,我對大寫十六進位制程式碼有偏好,如果您想使用小寫的Digest String只需要把byteHEX函式中的A、B、C、D、E、F改成a、b、 c、d、e、f就可以了。

MD5據稱是一種比較耗時的計算,我們的Java版MD5一閃就算出來了,沒遇到什麼障礙,而且用肉眼感覺不出來Java版的MD5比C版的慢。

為了測試它的相容性,我把這個MD5.class檔案拷貝到我的另一臺Linux+IBM JDK 1.3的機器上,執行後得到同樣結果,確實是“一次編寫到處運行了”。

Java Bean簡述


現在,我們已經完成並簡單測試了這個Java Class,我們文章的標題是做一個Java Bean。

其實普通的Java Bean很簡單,並不是什麼全新的或偉大的概念,就是一個Java的Class,儘管 Sun規定了一些需要實現的方法,但並不是強制的。而EJB(Enterprise Java Bean)無非規定了一些必須實現(非常類似於響應事件)的方法,這些方法是供EJB Container使用(呼叫)的。

在一個Java Application或Applet裡使用這個bean非常簡單,最簡單的方法是你要使用這個類的原始碼工作目錄下建一個beartool目錄,把這個 class檔案拷貝進去,然後在你的程式中import beartool.MD5就可以了。最後打包成.jar或.war是保持這個相對的目錄關係就行了。

Java還有一個小小的好處是你並不需要摘除我們的MD5類中那個main方法,它已經是一個可以工作的Java Bean了。Java有一個非常大的優點是她允許很方便地讓多種執行形式在同一組程式碼中共存,比如,你可以寫一個類,它即是一個控制檯 Application和GUI Application,同時又是一個Applet,同時還是一個Java Bean,這對於測試、維護和釋出程式提供了極大的方便,這裡的測試方法main還可以放到一個內部類中,有興趣的讀者可以參考:
http://help.liangjing.org/Tools/MD5.aspx
這裡講述了把測試和示例程式碼放在一個內部靜態類的好處,是一種不錯的工程化技巧和途徑。

把Java Bean裝到JSP裡


正如我們在本文開頭講述的那樣,我們對這個MD5 Bean的應用是基於一個使用者管理,這裡我們假設了一個虛擬社群的使用者login過程,使用者的資訊儲存在資料庫的個名為users的表中。這個表有兩個字 段和我們的這個例子有關,userid :char(20)和pwdmd5 :char(32),userid是這個表的Primary Key,pwdmd5儲存密碼的MD5串,MD5值是一個128bit的大整數,表示成16進位制的ASCII需要32個字元。

這裡給出兩個檔案,login.html是用來接受使用者輸入的form,login.jsp用來模擬使用MD5 Bean的login過程。

為了使我們的測試環境簡單起見,我們在JSP中使用了JDK內建的JDBC-ODBC Bridge Driver,community是ODBC的DSN的名字,如果你使用其它的JDBC Driver,替換掉login.jsp中的
Connection con= DriverManager.getConnection("jdbc:odbc:community", "", "");
即可。

login.jsp的工作原理很簡單,通過post接收使用者輸入的UserID和Password,然後將Password變換成MD5串,然後在 users表中尋找UserID和pwdmd5,因為UserID是users表的Primary Key,如果變換後的pwdmd5與表中的記錄不符,那麼SQL查詢會得到一個空的結果集。

這裡需要簡單介紹的是,使用這個Bean只需要在你的JSP應用程式的WEB-INF/classes下建立一個beartool目錄,然後將 MD5.class拷貝到那個目錄下就可以了。如果你使用一些整合開發環境,請參考它們的deploy工具的說明。在JSP使用一個java Bean關鍵的一句宣告是程式中的第2行:

<jsp:useBean id='oMD5' scope='request' class='beartool.MD5'/>
這是所有JSP規範要求JSP容器開發者必須提供的標準Tag。

id=實際上是指示JSP Container建立Bean的例項時用的例項變數名。在後面的<%和%>之間的Java程式中,你可以引用它。在程式中可以看到,通過 pwdmd5=oMD5.getMD5ofStr (password)引用了我們的MD5 Java Bean提供的唯一一個公共方法: getMD5ofStr。

Java Application Server執行.JSP的過程是先把它預編譯成.java(那些Tag在預編譯時會成為java語句),然後再編譯成.class。這些都是系統自動完 成和維護的,那個.class也稱為Servlet。當然,如果你願意,你也可以幫助Java Application Server去幹本該它乾的事情,自己直接去寫Servlet,但用Servlet去輸出HTML那簡直是回到了用C寫CGI程式的惡夢時代。

如果你的輸出是一個複雜的表格,比較方便的方法我想還是用一個你所熟悉的HTML編輯器編寫一個“模板”,然後在把JSP程式碼“嵌入”進去。儘管這種 JSP程式碼被有些專家指責為“空心粉”,它的確有個缺點是程式碼比較難管理和重複使用,但是程式設計永遠需要的就是這樣的權衡。我個人認為,對於中、小型項 目,比較理想的結構是把資料表示(或不嚴格地稱作WEB介面相關)的部分用JSP寫,和介面不相關的放在Bean裡面,一般情況下是不需要直接寫 Servlet的。

如果你覺得這種方法不是非常的OO(Object Oriented),你可以繼承(extends)它一把,再寫一個bean把使用者管理的功能包進去。

到底能不能相容?


我測試了三種Java應用伺服器環境,Resin 1.2.3、Sun J2EE 1.2、IBM WebSphere 3.5,所幸的是這個Java Bean都沒有任何問題,原因其實是因為它僅僅是個計算程式,不涉及作業系統,I/O裝置。其實用其它語言也能簡單地實現它的相容性的,Java的唯一優 點是,你只需提供一個形態的執行碼就可以了。請注意“形態”二字,現在很多計算結構和作業系統除了語言本身之外都定義了大量的程式碼形態,很簡單的一段C語 言核心程式碼,轉換成不同形態要考慮很多問題,使用很多工具,同時受很多限制,有時候學習一種新的“形態”所花費的精力可能比解決問題本身還多。比如光 Windows就有EXE、Service、的普通DLL、COM DLL以前還有OCX等等等等,在Unix上雖說要簡單一些,但要也要提供一個.h定義一大堆巨集,還要考慮不同平臺編譯器版本的位長度問題。我想這是 Java對我來說的一個非常重要的魅力吧。


MD5演算法說明

一、補位
二、補資料長度
三、初始化MD5引數
四、處理位操作函式
五、主要變換過程
六、輸出結果


補位:
MD5演算法先對輸入的資料進行補位,使得資料位長度LEN對512求餘的結果是448。即資料擴充套件至K*512+448位。即K*64+56個位元組,K為整數。
具體補位操作:補一個1,然後補0至滿足上述要求。
補資料長度:
用一個64位的數字表示資料的原始長度B,把B用兩個32位數表示。這時,數
據就被填補成長度為512位的倍數。
初始化MD5引數:
四個32位整數 (A,B,C,D) 用來計算資訊摘要,初始化使用的是十六進位制表
示的數字
A=0X01234567
B=0X89abcdef
C=0Xfedcba98
D=0X76543210

處理位操作函式:
X,Y,Z為32位整數。
F(X,Y,Z) = X&Y|NOT(X)&Z
G(X,Y,Z) = X&Z|Y?(Z)
H(X,Y,Z) = X xor Y xor Z
I(X,Y,Z) = Y xor (X|not(Z))

主要變換過程:
使用常陣列T[1 ... 64], T[i]為32位整數用16進製表示,資料用16個32位
的整數陣列M[]表示。
具體過程如下:

/* 處理資料原文 */
For i = 0 to N/16-1 do

/*每一次,把資料原文存放在16個元素的陣列X中. */
For j = 0 to 15 do
Set X[j] to M[i*16+j].
end /結束對J的迴圈

/* Save A as AA, B as BB, C as CC, and D as DD.
*/
AA = A
BB = B
CC = C
DD = D

/* 第1輪*/
/* 以 [abcd k s i]表示如下操作
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */

/* Do the following 16 operations. */
[ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3
22 4]
[ABCD 4 7 5] [DABC 5 12 6] [CDAB 6 17 7] [BCDA 7
22 8]
[ABCD 8 7 9] [DABC 9 12 10] [CDAB 10 17 11] [BCDA
11 22 12]
[ABCD 12 7 13] [DABC 13 12 14] [CDAB 14 17 15]
[BCDA 15 22 16]

/* 第2輪* */
/* 以 [abcd k s i]表示如下操作
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 1 5 17] [DABC 6 9 18] [CDAB 11 14 19] [BCDA
0 20 20]
[ABCD 5 5 21] [DABC 10 9 22] [CDAB 15 14 23]
[BCDA 4 20 24]
[ABCD 9 5 25] [DABC 14 9 26] [CDAB 3 14 27] [BCDA
8 20 28]
[ABCD 13 5 29] [DABC 2 9 30] [CDAB 7 14 31] [BCDA
12 20 32]

/* 第3輪*/
/* 以 [abcd k s i]表示如下操作
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 5 4 33] [DABC 8 11 34] [CDAB 11 16 35]
[BCDA 14 23 36]
[ABCD 1 4 37] [DABC 4 11 38] [CDAB 7 16 39] [BCDA
10 23 40]
[ABCD 13 4 41] [DABC 0 11 42] [CDAB 3 16 43]
[BCDA 6 23 44]
[ABCD 9 4 45] [DABC 12 11 46] [CDAB 15 16 47]
[BCDA 2 23 48]

/* 第4輪*/
/* 以 [abcd k s i]表示如下操作
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 0 6 49] [DABC 7 10 50] [CDAB 14 15 51]
[BCDA 5 21 52]
[ABCD 12 6 53] [DABC 3 10 54] [CDAB 10 15 55]
[BCDA 1 21 56]
[ABCD 8 6 57] [DABC 15 10 58] [CDAB 6 15 59]
[BCDA 13 21 60]
[ABCD 4 6 61] [DABC 11 10 62] [CDAB 2 15 63]
[BCDA 9 21 64]

/* 然後進行如下操作 */
A = A + AA
B = B + BB
C = C + CC
D = D + DD

end /* 結束對I的迴圈*/

輸出結果。