MD5(Message-Digest Algorithm 5,信息-摘要算法5)用于确保信息传输完整一致,是计算机广泛使用的杂凑算法之一(又译摘要算法,哈希算法)。MD5算法将数据(如汉字)运算成另一固定长度值,MD5的前生有MD2,MD3和MD4。
MD5算法具有如下特征:
1、压缩性:任意长度的数据算出的MD5值是固定的,即128 位固定长度的散列值。
2、容易计算:从原数据很容易算出MD5值。
3、抗修改性:对原数据任何一个细微的改动,所得到的MD5值都会有很大的不同。
4、强抗碰撞:MD5 使用的是散列函数(也称哈希函数),一定概率上也存在哈希冲突(也称哈希碰撞),即多个不同的原数据对应一个相同的 MD5 值。不过,经过 MD4、MD3 等几代算法的优化,MD5 已经充分利用散列的分散性高度避免碰撞的发生。
可以看出,MD5 是一种不可逆的算法,也就说,你无法通过得到的 MD5 值逆向算出原数据内容。正是凭借这些特点,MD5 被广泛使用。
1 MD5的使用场景
客户端与服务器的 HTTP 通信,通信双方可以将报文内容做一个 MD5 计算,并将计算所得 MD5 值一并传递给彼此,这样,接收方可以通过对报文内容再次做 MD5 计算得到一个 MD5 值,与传递报文中的 MD5 值做比较,验证数据是否完整,或者是否中途被拦截篡改过。
网络云盘中的文件秒传功能也运用到 MD5 算法。服务器存储文件的时候,同时记录每一个文件的 MD5 值,不同文件对应着不同的 MD5 值。这样,遇到用户上传文件时,将上传文件的 MD5 值与服务器上所有存储的 MD5 值做比较,如果相同,则说明用户上传的文件已经在服务器存有。这样,只需要在数据库表中添加一个记录,映射到对应的文件,而不用重复上传,实现所谓秒传的功能。
注意:严格意义上来讲,MD5 并不属于加密算法,也不属于签名算法,而是一种摘要算法,用于数据完整性校验等。
2 Java 语言中计算 MD5 值的实现方式
第一步,获取 MessageDigest 对象,参数为 MD5 字符串,表示这是一个 MD5 算法(其他还有 SHA1 算法等):
1 | MessageDigest md5 = MessageDigest.getInstance("MD5"); |
第二步,输入原数据,参数类型为 byte[] :
1 | md5.update(buffer); |
注意:update() 方法有点类似 StringBuilder 对象的 append() 方法,采用的是追加模式,属于一个累计更改的过程,比如:
1 | md5.update(new byte[]{'a', 'b'}); |
与
1 | md5.update(new byte[]{'a','b','c', 'd'}); |
是等效的。
第三步,计算 MD5 值:
1 | String resultArray = md5.digest(); |
注意:digest() 方法被调用后,MessageDigest 对象就被重置,也就是说你不能紧接着再次调用该方法计算原数据的 MD5 值。当然,你可以手动调用 reset() 方法重置输入源。
digest() 方法返回值是一个字节数组类型的 16 位长度的哈希值,通常,我们会转化为十六进制的 32 位长度的字符串来使用,可以利用 BigInteger 类来做这个转化:
1 | BigInteger bigInt = new BigInteger(1, resultArray); |
通过这层转换,得到的 MD5 值便是一个长度为 32 位的十六进制字符串,方便使用,类似这样:
1 | 15aa7ec97a6288e46ae865a7211eb4eb |
有了上面的基础我们再来看如何将一个文件通过MD5加密转化成32位的十六进制字符串。
1 | public static String fileToMD5(String filePath) { |
注意:文件的大小直接影响字节流的读取速度,间接影响这里 MD5 的计算时长。Java 语言提供有多种方式读取文件,除了上面用到的 FileInputStream 这种顺序读取的 API 类,还有采用随机读取方式的 RandomAccessFile 类等。对于文件读取的效率问题,研究之后再作介绍。