在文件上传下载相关的操作中,我们通常会遇到需要计算文件MD5的场景,计算文件MD5值的方法和计算字符串的MD5值有些类似,这里先来介绍普通的计算字符串的MD5方法。
commons-codec这个jar给我们提供了一个MD5实现,普通的MD5实现,基本大同小异,最终的结果也相同,这里不能说的太过,为什么称为普通的MD5,因为实现思路是一样的,而且只要是相同的字符串,计算的结果也一样,因此也很容易被破解,虽然MD5这个算法本身是不可逆的,但是根据彩虹表的破解方式,或者在线破解,一些常用的密码很容易被猜到。
先来看看commons-codec提供的MD5的实现:
DigestUtils.md5Hex(String source);
就这么一行简单的代码就帮我们实现了MD5算法。
也有很多不用commons-codec提供的api,借助java security api实现MD5算法的,这里给出一个简单的示例:
private static final String[] strHex=
{"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
private static MessageDigest digest = null;
static{
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public static String getMD5(String source){
try {
byte[] data = source.getBytes("UTF-8");
return buffer2Hex(digest.digest(data));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static String buffer2Hex(byte[] data){
StringBuffer sb = new StringBuffer();
for(int i=0;i<data.length;i++){
int d = data[i];
if(d<0){
d+=256;
}
int d1 = d / 16;
int d2 = d % 16;
sb.append(strHex[d1]+strHex[d2]);
}
return sb.toString();
}
相信大家常见的MD5算法就是长这个样子的,其中,我们看到的复杂的部分就是在进行字节数组转十六进制。其实java提供的api已经帮助我们完成了MD5核心算法,我们这里进行的字节数组转16进制,其实是这个算法的冰山一角。
当然字节数组转16进制,也有很多办法,所以这个方法也有很多变种:
public static String buffer2Hex3(byte[] data){
StringBuffer sb = new StringBuffer();
for(int i=0;i<data.length;i++){
int d1 = data[i];
if(d1<0){
d1 = d1 & 0xff;
}
if(d1<16){
sb.append(“0”);
}
sb.append(Integer.toHexString(d1));
}
return sb.toString();
}
借助BigInteger直接转16进制:
public static String buffer2Hex4(byte[] data){
BigInteger res = new BigInteger(1, data);
return res.toString(16);
}
当然也有利用字符数组来替换前面的字符串数组的算法:
private static final char[] digestHex={‘0′,’1′,’2′,’3′,’4′,’5′,’6′,’7′,’8′,’9′,’a’,’b’,’c’,’d’,’e’,’f’};
public static String buffer2Hex2(byte[] data){
StringBuffer sb = new StringBuffer();
for(int i=0;i<data.length;i++){
byte d = data[i];
char d1 = digestHex[(d & 0xf0) >> 4];
char d2 = digestHex[d & 0xf];
sb.append(d1);
sb.append(d2);
}
return sb.toString();
}
我们知道,这些不同的实现,核心的算法是字节数组转十六进制。那么对于文件而言,如何计算MD5,我们可以试想,如果把文件转为字节数组了,是不是就可以套用这个后面字节数组转十六进制的方法了。事实上,确实如此,我们只需要将文件读入内存,然后将其转为字节数组,剩下的就是和求字符串的MD5方法一样了。
commons-io提供了FileUtils.readFileToByteArray(File file)这个api,得到的结果就是byte[]类型的字节数组。所以求文件的MD5算法就有了这样简单的实现了:
public static String md5Hex(File file) {
try {
return DigestUtils.md5Hex(FileUtils.readFileToByteArray(file));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
如果你有兴趣,可以将FileUtils.readFileToByteArray(file)方法替换为自己编写的一个简单的实现:通过IO流的方式将文件读入内存,然后将其转为byte[]类型的数组。
常见的字符串经过MD5加密之后的结果:
- 123456->e10adc3949ba59abbe56e057f20f883e
- admin ->21232f297a57a5a743894a0e4a801fc3
- 111111->96e79218965eb72c92a549dd5a330112
- 666666->f379eaf3c831b04de153469d1bec345e
我们通过MessageDigest.digest(byte[]) 得到的数组,无论原始字符串或者文件多大,得到的字节数组,长度都是16,因此根据10进制转为16进制,结果都是32位的。再次说明了,普通的MD5算法还是有可能被破解的。