MD5算法的實現詳解


提起加密,很多人會將MD5也列舉出來,說MD5加密,這樣說其實是不嚴謹的,不正確的。Message Digest Algorithm MD5(中文名爲消息摘要算法第五版)是一種摘要算法(首先名字裏面都沒有帶加密的字眼),單向的,不可逆的,它的功能通常是用來驗證文件或是數據的完整性。經它處理過的數據,外表給人的感覺是被“加密”成了難以識別的字符串,但是它並不能被稱爲加密算法,有一個通俗一點的理解方式爲:加密算法肯定有對應的解密算法,但MD5沒有,所以不能稱爲加密。接下來我們就來聊聊MD5的具體實現。


 首先MD5的實現需要一個非常重要的類——MessageDigest,其在oracle官網的解釋是:MessageDigest類是一個引擎類,用以提供加密的安全消息摘要功能(如SHA-1或MD5等)。密碼安全的消息摘要採用任意大小的輸入(字節數組),並生成稱爲摘要或散列的固定大小的輸出。摘要有兩個屬性:

1、找到兩個散列成相同值的消息在計算上是不可行的。
2、摘要不可以揭示用於生成它的輸入的任何內容。
 消息摘要用於生成唯一可靠的數據標識符。它們有時被稱爲數據的“數字指紋”。
 
RFC 1321是“MD5 報文摘要算法”的文件號,其中的解釋爲:MD5 報文摘要算法將任意長度的信息作爲輸入值,並將其換算成一個 128 位長度的"指紋信息"或"報文摘要"值來代表這個輸入值,並以換算後的值作爲結果。MD5 算法主要是爲數字簽名應用程序而設計的;在這個數字簽名應用程序中,較大的文件將在加密(這裏的加密過程是通過在一個密碼系統下[如:RSA]的公開密鑰下設置私有密鑰而完成的)之前以一種安全的方式進行壓縮(就是把一個任意長度的字節串變換成一定長的十六進制數字串)。 MD5算法具有以下特點:
1、壓縮性:任意長度的數據,算出的MD5值長度都是固定的。
2、容易計算:從原數據計算出MD5值很容易。
3、抗修改性:對原數據進行任何改動,哪怕只修改1個字節,所得到的MD5值都有很大區別。

4、強抗碰撞:已知原數據和其MD5值,想找到一個具有相同MD5值的數據(即僞造數據)是非常困難的。


我簡單的說一下MD5算法驗證的原理(如果有筒靴想了解更詳細的內容,請自行百度,我們今天只是實現的詳解):MD5驗證在平常我們的生活中隨處可見,比如說:APP的賬號登錄,銀行卡的密碼等等。就拿我們的銀行卡密碼來說,這是個人的隱私,除了自己任何人都不能得到,包括銀行,那麼當我們取錢時輸入密碼,銀行是怎麼知道我們的密碼是對的,這正好用到我們的MD5算法。當我們開戶時設置密碼時,密碼時被MD5進行了數據處理,然後存儲到銀行系統的數據庫中,當我們取錢時,輸入密碼後,密碼會被常用與之前同樣的算法進行同樣步驟的處理,然後,銀行會拿數據庫中存的處理過的密碼和我們現在輸入的處理過的密碼進行對比,如果一模一樣了,就給我們打開我們賬戶所有的權限,我們就可以對我們的賬戶爲所欲爲了,否則我們是任何操作的做不了的。據說銀行將我們的密碼進行了九次MD5算法處理,根據它的特點屬性我們可以知道,一次都已經無法破解了,何況是九次,自己想象吧。


計算摘要的步驟爲:

一、創建一個消息摘要實例

與所有引擎類一樣,MessageDigest也是通過調用getInstance方法來獲取特定類型的消息摘要算法的對象,該getInstance方法是MessageDigest類中的靜態工廠方法:
static MessageDigest getInstance(String algorithm);
注意:算法名稱不區分大小寫。
調用者可以選擇指定提供者的名稱或提供者實例 ,其保證所請求的算法的實現來自指定的提供者:
// algorithm:指定算法名稱; provider:指定提供者的名稱或提供者實例 
① static MessageDigest getInstance(String algorithm,String provider)
② static MessageDigest getInstance(String algorithm,Provider provider)
調用getInstance返回初始化消息摘要對象。因此不需要進一步的初始化。
根據oracle官網上的Java Cryptography Architecture中的建議:如果不是有特殊要求必須得指定提供者,儘量不要指定提供者或者提供者實例,我們只需要瞭解有這種實例化的方法就行了

二、更新消息摘要對象,將數據傳入

該步操作是爲了將要進行摘要算法的數據提供給初始化的消息摘要對象。這是通過調用以下update()方法之一完成的:

方法一:

// 使用指定的字節更新摘要。input - 用於更新摘要的字節。
void update(byte input);
方法二:
// 使用指定的字節數組更新摘要。input - 字節數組。
void update(byte [] input);
方法三:
// 使用指定的字節數組,從指定的偏移量開始更新摘要。input - 字節數組;offset - 字節數組中的偏移量,操作從此處開始;len - 要使用的字節數。
void update(byte [] input,int offset,int len);

三、計算摘要

通過調用update方法提供數據後,將使用對以下digest方法之一的調用來計算摘要 :

方法一:

// 通過執行諸如填充之類的最終操作完成哈希計算。調用此方法後摘要被重置。
// byte [] - 存放哈希值結果的字節數組
byte [] digest();
方法二:
// 使用指定的字節數組對摘要進行最後更新,然後完成摘要計算。也就是說,此方法首先調用 update(input),向 update 方法傳遞 input 數組,然後調用 digest()。
// input - 在完成摘要計算前要更新的輸入。
byte [] digest(byte [] input);
方法三:
// 通過執行諸如填充之類的最終操作完成哈希計算。調用此方法後摘要被重置。
// buf - 存放計算摘要的輸出緩衝區; offset - 輸出緩衝區中的偏移量,從此處開始存儲摘要; len - 在 buf 中分配給摘要的字節數
int digest(byte [] buf,int offset,int len);

前兩種方法返回計算的摘要,爲字節數組類型。後一種方法將計算的摘要存儲在提供的緩衝區中 buf,從offset開始,len是buf分配給摘要的字節數。該方法返回實際存儲在buf中的字節數。

四、實例

好了,所有的步驟就這些了,我們現在來個例子總結一下,例子的主要內容就是:將一個任意想要處理的字符串進行MD5算法處理,然後輸出處理後的字符串。

代碼走起:

public static void main(String[] args) {
		
		try {
			String string = "待處理^de_數據(Note: You kan type in whatever you want!)";
			System.out.println("處理前的數據: " + string);
			// 創建一個消息摘要實例
			MessageDigest digester = MessageDigest.getInstance("MD5");
			// 更新消息摘要對象,將數據傳入
			digester.update(string.getBytes());
			// 將傳進來的String 變成byte數組進行摘要獲取
			byte[] data = digester.digest();
			// 將獲取的摘要信息 獲取 出來,byte中存的默認是16進制,進行轉換(在這兒不能將byte[]與String互轉),轉換爲Android直接可以使用的String ,char
			String mData = toHex(data);
			System.out.println("處理後的數據: " + mData);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
裏面用到一個轉換方法toHex(data),其代碼爲:

	/**
	 * 字符串/字節數組 ——> 十六進制數字的字符串
	 * 
	 * @param data	待處理的字節數組
	 * @return		其對應的十六進制字符串
	 */
	private static String toHex(byte[] data) {
		String str = null;
		if (null != data) {
			StringBuilder sBuilder = new StringBuilder();
			for(byte b: data) {
				// %2x 代表的就是 16進制的格式
				sBuilder.append(String.format("%2x", b));
			}
			str = sBuilder.toString();
			return str;
		}
		return null;
	}
輸出結果:

處理前的數據: 待處理^de_數據(Note: You kan type in whatever you want!)
處理後的數據: bcf097d2d42859a5ae82a37efc93cc1a

擴展:

注意:消息摘要計算完畢後,消息摘要對象將自動復位並準備好接收新數據並計算其摘要。所有以前的狀態(即:提供給update 的數據)都將丟失。當然,凡事都不是絕對的,有一些哈希實現(不要迷了這兒爲什麼用哈希,別忘了摘要算法的整個過程都是將我們傳入的數據變爲最終的hash值)可以通過克隆來支持中間哈希(也就是之前的狀態不被丟失)。

1、接下來,假設我們有三個字節數組:i1,i2 和i3,形成總輸入,我們要計算這個總輸入的消息摘要。此摘要(或“哈希”)可以通過以下方式計算:

sha.update(i1); 
sha.update(i2); 
sha.update(i3); 
byte [] hash = sha.digest();

還可以等效爲:

sha.update(i1); 
sha.update(i2); 
byte[] hash = sha.digest(i3);

消息摘要計算完畢後,消息摘要對象將自動復位並準備好接收新數據並計算其摘要。所有以前的狀態(即:提供給update 的數據)都將丟失。
2、假設我們要計算幾個單獨的哈希值:i1;i1 and i2;i1, i2, and i3

一個辦法是:

// 計算i1的哈希值
sha.update(i1); 
byte[] i1Hash = sha.clone().digest(); 
				
// 計算i1和i2的哈希值
sha.update(i2); 
byte[] i12Hash = sha.clone().digest(); 
				
// 計算i1,i2和i3的哈希值
sha.update(i3); 
byte[] i123hash = sha.digest();

該代碼只有在SHA-1實現可克隆時纔有用。雖然消息摘要的一些實現是可克隆的,但有些則不是。要確定是否可以進行克隆,請嘗試克隆MessageDigest對象並捕獲潛在的異常,代碼如下所示:

try {
	// 嘗試克隆它
	// 計算i1的哈希值
	sha.update(i1); 
	byte[] i1Hash = sha.clone().digest();
	 . . .
	byte[] i123hash = sha.digest();
} catch (CloneNotSupportedException cnse) {
	// 執行其他操作,如下面所示的代碼
}

如果消息摘要不是可克隆的,那麼另一個不那麼優雅的計算中間摘要的方法是創建幾個摘要。在這種情況下,必須提前知道要計算的中間摘要數量,代碼如下:

MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
MessageDigest sha12 = MessageDigest.getInstance("SHA-1"); 
MessageDigest sha123 = MessageDigest.getInstance("SHA-1");
			
// 計算i1的哈希值
byte[] i1Hash = sha1.digest(i1);
			
// 計算i1和i2的哈希值
sha12.update(i1);
byte[] i12Hash = sha12.digest(i2);
			
// 計算i1,i2和i3的哈希值
sha123.update(i1);
sha123.update(i2);
byte[] i123Hash = sha123.digest(i3);

有沒有覺得代碼很眼熟,嘿嘿,想知道就往上找吧。

如有不足之處,還望留言指正,多謝各位了!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章