原文地址: http://www.blogjava.net/yangpingyu/archive/2012/01/10/368211.html
(一)问题
项目中需要对文件做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即可,更加方便,可靠。