转载:亦枫 – http://yifeng.studio/2017/06/13/md5-profile-and-implementation-in-java/
MD5基本概念
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中的实现方式
- 获取MessageDigest对象,参数为MD5字符串,标识这是一个MD5算法(其它还有SHA1算法等):
MessageDigest md5 = MessageDigest.getInstance("MD5");
- 输入原数据,参数类型为byte[]:
md5.update(buffer);
注意:update()方法有点类似StirngBuilder对象的append()方法,采用的是追加模式,属于一个累计更改的过程,比如:
md5.update(new byte[]{'a','b'});
md5.update(new byte[]{'b','c'});
与
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 = nwe 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 类等。关于各种读取方式的效率,推荐大家阅读这篇文章:
/**
* MD5加密算法工具类
*
*/
public class MD5Utils {
/**
* 加盐
* @param length
* @return
*/
public static String getComplexRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* MD5加密
* @param plainText
* @return
*/
public static String getMD5Encode(String plainText){
if(StringUtil.isNotBlank(plainText)){
//定义一个字节数组
byte[] secretBytes = null;
try {
// 生成一个MD5加密计算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
//对字符串进行加密
md.update(plainText.getBytes());
//获得加密后的数据
secretBytes = md.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
//将加密后的数据转换为16进制数字
String md5code = new BigInteger(1, secretBytes).toString(16);
// 如果生成数字未满32位,需要前面补0
int num = 32;
String str = "0";
for (int i = 0; i < num - md5code.length(); i++) {
md5code = str + md5code;
}
return md5code;
}else {
String str = "密码不能为空";
return str;
}
}
}
Java tip: How to read files quickly