MD5在Java中的实现方式

作者: Ian | 2018-08-03 | 阅读

转载:亦枫 – http://yifeng.studio/2017/06/13/md5-profile-and-implementation-in-java/

MD5基本概念

MD5,Message Digest Algorithm 5,是一种被广泛使用的信息摘要算法,可以将给定的任意长度数据通过一定的算法得出一个128位固定长度的散列值。如百科介绍MD5具有如下特点:

  1. 压缩性:任意长度的元数据,其MD5值都是一个固定的,即128位;
  2. 易计算:计算原数据的MD5值是一个比较容易的过程;
  3. 抗修改:原数据的任意改动,所得到的MD5值都是迥然不同的;
  4. 防碰撞:这一点要特别介绍下。MD5使用的是散列函数(也称哈希函数),一定概率上也存在哈希冲突(也称哈希碰撞),即多个不同的原数据对应一个相同的MD5值。不过,经过MD4、MD3等几代算法的优化,MD5已经充分利用散列的分散性高度避免碰撞发生。

可以看出,MD5是一种不可逆的算法,也就是说你无法通过得到的MD5值逆向算出原数据内容。正是凭借这些特点,MD5被广泛使用。

比如,客户端与服务器的HTTP通信,通信双方可以将报文内容做一个MD5计算,并将计算得到MD5值一并传递给彼此,这样,接收方可以通过对报文内容再次做MD5计算得到一个MD5值,与传递报文中的MD5值作比较,验证数据是否完整,或者是否中途被拦截篡改过。

再比如,网络云盘中的文件秒传功能运用到MD5算法。服务器存储文件的时候,同时记录每一个文件的MD5值,不同文件对应着不同的MD5值。这样,遇到用户上传文件时,将上传文件的MD5值与服务器上所有存储的MD5值做比较,如果相同,则说明用户上传的文件已在服务器存有。这样,只需要在数据库表中添加一个记录,映射到对应的文件,而不用重复上传,实现所谓的秒传功能。

当然,这只是常见的两个例子,MD5的用途大有所在。值得注意的是,严格意义上来讲,MD5以及SHA1并不属于加密算法,也不属于前面算法,而是一种摘要算法,用于数据完整性校验等。

MD5在Java中的实现方式

  1. 获取MessageDigest对象,参数为MD5字符串,标识这是一个MD5算法(其它还有SHA1算法等):
MessageDigest md5 = MessageDigest.getInstance("MD5");
  1. 输入原数据,参数类型为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'})

是等效的,计算结果相同。

  1. 计算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



  相关文章:


留言区:

TOP