(一)問題
項目中需要對文件做md5sum,分兩步走:1、對文件流的每個字節用md5實例進行update,然后進行digest。2、digest返回長度為16的byte數組,一般我們需要把byte數組轉成16進制字符串(很多開源的md5加密算法如此實現,真正的原因還不是很理解,可能是便于查看和傳輸)。具體的實現代碼如下:
/**
* 對文件進行md5 sum操作
* @param checkFile 要進行做md5 sum的文件
* @return
*/
public static String md5sum(File checkFile){
String md5sumResult = "";
if(checkFile == null || (!checkFile.exists())){
return md5sumResult;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(checkFile);
byte[] buffer = new byte[8192];
int read = 0;
try {
while( (read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
md5sumResult = bigInt.toString(16);
}
catch(IOException e) {
throw new RuntimeException("Unable to process file for MD5", e);
}
finally {
try {
is.close();
}
catch(IOException e) {
throw new RuntimeException("Unable to close input stream for MD5 calculation", e);
}
}
return md5sumResult;
}
其中黃色背景色的轉換方式是有問題的。為什么用bigint轉16進制會有問題呢,原因是bigint進行16進制轉換的時候第一個0被自動去掉了.
(二)正確解決方式
那正確的方式是怎么樣的呢?下面有兩種不同的轉換方式,但是原理其實是一致的。
第一種正確的方式(由王建提供):
/**
* 將字節數組轉換為16進制字符串
*
* @param buffer
* @return
*/
public static String toHex(byte[] buffer) {
StringBuffer sb = new StringBuffer(buffer.length * 2);
for (int i = 0; i < buffer.length; i++) {
sb.append(Character.forDigit((buffer[i] & 240) >> 4, 16));
sb.append(Character.forDigit(buffer[i] & 15, 16));
}
return sb.toString();
}
第二種正確的方式:
public static String bytes2HexString(byte[] b) {
String ret = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
ret += hex;
}
return ret;
}
(三)問題分析
Md5算法對任何長度的字符串進行編碼最后輸出是128位長整數,也就是長度為16的byte數組。我們項目調用的是jdk實現的md5算法,所以一般是沒問題的。
接下來我們要處理的事情,分別循環數組,把每個字節轉換成2個16進制字符,也就是說每4位轉成一個16進制字符。
上面正確的兩種方式也就是做了這樣的事情。
第一種方式:
Character.forDigit((buffer[i] & 240) >> 4, 16)把字節的高4位取出右移4位換算成int,然后通過forDigit轉換成16進制字符
Character.forDigit(buffer[i] & 15, 16)把字節的低4位取出換算成int,然后通過forDigit轉換成16進制字符
第二種方式:
Integer.toHexString(b[i] & 0xFF)把整個字節轉成int,然后toHexString也就是做高4位和低4位的運算。但是這個方法如果高四位是0的話就不輸出任何東西,
所以在輸出的字符前加0即可。
b[i] & 0xFF就是把byte轉成int,為什么用與oxff做與運算,是因為如果b[i]是負數的話,從8位變成32位會補1,所以需要與0xff做與運算,可以把前面的24位全部清零,又可以表示成原來的字節了。
附:
盡量使用開源提供的工具包,比如:
org.apache.commons.codec.digest.DigestUtils.md5Hex(InputStream data)來對文件流進行md5即可,更加方便,可靠。