MD5,Message Digest Algorithm 5,是一種被廣泛使用的信息摘要算法,可以將給定的任意長度數據通過一定的算法計算得出一個 128 位固定長度的散列值。如百科介紹,MD5 具有如下特點:
壓縮性:任意長度的原數據,其 MD5 值都是固定的,即 128 位;
易計算:計算原數據的 MD5 值是一個比較容易的過程;
抗修改:原數據的任意改動,所得到的 MD5 值都是迥然不同的;
防碰撞:這一點要特別介紹一下。MD5 使用的是散列函數(也稱哈希函數),一定概率上也存在哈希衝突(也稱哈希碰撞),即多個不同的原數據對應一個相同的 MD5 值。不過,經過 MD4、MD3 等幾代算法的優化,MD5 已經充分利用散列的分散性高度避免碰撞的發生。
可以看出,MD5 是一種不可逆的算法,也就說,你無法通過得到的 MD5 值逆向算出原數據內容。正是憑藉這些特點,MD5 被廣泛使用。
比如,客戶端與伺服器的 HTTP 通信,通信雙方可以將報文內容做一個 MD5 計算,並將計算所得 MD5 值一併傳遞給彼此,這樣,接收方可以通過對報文內容再次做 MD5 計算得到一個 MD5 值,與傳遞報文中的 MD5 值做比較,驗證數據是否完整,或者是否中途被攔截篡改過。
再比如,網絡雲盤中的文件秒傳功能也運用到 MD5 算法。伺服器存儲文件的時候,同時記錄每一個文件的 MD5 值,不同文件對應著不同的 MD5 值。這樣,遇到用戶上傳文件時,將上傳文件的 MD5 值與伺服器上所有存儲的 MD5 值做比較,如果相同,則說明用戶上傳的文件已經在伺服器存有。這樣,只需要在資料庫表中添加一個記錄,映射到對應的文件,而不用重複上傳,實現所謂秒傳的功能。
當然,這只是常見的兩個例子,MD5 的用途大有所在。值得注意的是,嚴格意義上來講,MD5 以及 SHA1 並不屬於加密算法,也不屬於籤名算法,而是一種摘要算法,用於數據完整性校驗等。
了解完基本的 MD5 概念,再來看看 Java 語言中計算 MD5 值的實現方式。
第一步,獲取 MessageDigest 對象,參數為 MD5 字符串,表示這是一個 MD5 算法(其他還有 SHA1 算法等):
MessageDigest md5 = MessageDigest.getInstance("MD5");
第二步,輸入原數據,參數類型為 byte[] :
注意:update() 方法有點類似 StringBuilder 對象的 append() 方法,採用的是追加模式,屬於一個累計更改的過程,比如:
md5.update(new byte[]{'a', 'b'});
md5.update(new byte[]{'c', 'd'});
與
md5.update(new byte[]{'a', 'b', 'c', 'd'});
是等效的,計算結果相同。
第三步,計算 MD5 值:
String resultArray = md5.digest();
注意:digest() 方法被調用後,MessageDigest 對象就被重置,也就是說你不能緊接著再次調用該方法計算原數據的 MD5 值。當然,你可以手動調用 reset() 方法重置輸入源。
digest() 方法返回值是一個字節數組類型的 16 位長度的哈希值,通常,我們會轉化為十六進位的 32 位長度的字符串來使用,可以利用 BigInteger 類來做這個轉化:
BigInteger bigInt = new BigInteger(1, resultArray);
String resultStr = bigInt.toString(16);
當然,還有很多其它方式也能實現字節數組到十六進位的轉換,比如通過位運算:
public static String byteArrayToHex(byte[] byteArray) {
char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' };
char[] resultCharArray =new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b>>>4 & 0xf];
resultCharArray[index++] = hexDigits[b & 0xf];
}
return new String(resultCharArray);
}
通過這層轉換,得到的 MD5 值便是一個長度為 32 位的十六進位字符串,方便使用,類似這樣:
301d61853fc9ce94bbfb55b56c218d06
有了以上這些基礎知識,再來看看如何將文件轉化為一個 MD5 字符串:
public static String fileToMD5(String path){
try {
FileInputStream fis = new FileInputStream(path);
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
digest.update(buffer, 0, len);
}
fis.close();
BigInteger bigInt = new BigInteger(1, digest.digest());
return bigInt.toString(16);
} catch (IOException | NoSuchAlgorithmException e){
e.printStackTrace();
}
return "";
}
注意:文件的大小直接影響字節流的讀取速度,間接影響這裡 MD5 的計算時長。Java 語言提供有多種方式讀取文件,除了上面用到的 FileInputStream 這種順序讀取的 API 類,還有採用隨機讀取方式的 RandomAccessFile 類等。關於各種讀取方式的效率,推薦大家閱讀這篇文章: