6.2.3 实现

MD系列算法的实现是通过MessageDigest类来完成的,如果需要以流的处理方式完成消息摘要,则需要使用DigestInputStream和DigestOutputStream,有关Java API请读者朋友参照第3章内容。Java 6仅支持MD2和MD5两种算法,通过第三方加密组件包Bouncy Castle(详见第4章),可支持MD4算法。

MD系列算法支持如表6-1所示。

1.Sun

figure_0173_0040

在Java 6中使用MD算法是很简单的。例如,要使用MD5算法对数据做消息摘要,可参考如下代码:


//初始化MessageDigest,并指定MD5算法

MessageDigest md=MessageDigest.getInstance("MD5");

//摘要处理

byte[]b=md.digest(data);


在上述代码中,data[]为待做消息摘要处理的数据,b[]是经过消息摘要处理后的摘要信息,也就是数字指纹。

Java 6支持MD2和MD5算法,如要使用MD2算法只需替换算法名即可,相关实现如代码清单6-1所示。

代码清单6-1 MD2和MD5算法实现


import java.security.MessageDigest;

/**

*MD消息摘要组件

*@author梁栋

*@version 1.0

*@since 1.0

*/

public abstract class MDCoder{

/**

*MD2消息摘要

*@param data待做摘要处理的数据

*@return byte[]消息摘要

*@throws Exception

*/

public static byte[]encodeMD2(byte[]data)throws Exception{

//初始化MessageDigest

MessageDigest md=MessageDigest.getInstance("MD2");

//执行消息摘要

return md.digest(data);

}

/**

*MD5消息摘要

*@param data待做摘要处理的数据

*@return byte[]消息摘要

*@throws Exception

*/

public static byte[]encodeMD5(byte[]data)throws Exception{

//初始化MessageDigest

MessageDigest md=MessageDigest.getInstance("MD5");

//执行消息摘要

return md.digest(data);

}

}


消息摘要的主要特点就是对同一段数据做多次摘要处理后,其摘要值完全一致。因此,我们通过必要两次消息摘要的结果来判别消息摘要是否一致,见代码清单6-2。

代码清单6-2 MD2和MD5算法实现测试用例


import static org.junit.Assert.*;

import org.junit.Test;

/**

*MD校验

*@author梁栋

*@version 1.0

*@since 1.0

*/

public class MDCoderTest{

/**

*测试MD2

*@throws Exception

*/

@Test

public final void testEncodeMD2()throws Exception{

String str="MD2消息摘要";

//获得摘要信息

byte[]data1=MDCoder.encodeMD2(str.getBytes());

byte[]data2=MDCoder.encodeMD2(str.getBytes());

//校验

assertArrayEquals(data1,data2);

}

/**

*测试MD5

*@throws Exception

*/

@Test

public final void testEncodeMD5()throws Exception{

String str="MD5消息摘要";

//获得摘要信息

byte[]data1=MDCoder.encodeMD5(str.getBytes());

byte[]data2=MDCoder.encodeMD5(str.getBytes());

//校验

assertArrayEquals(data1,data2);

}

}


测试结果自然不用说,一定完全一致!

2.Bouncy Castle

第三方加密组件包Bouncy Castle是对Java 6的友善补充,不仅提供了Base64算法的实现,更是弥补了Sun未能提供MD4算法的空白。

使用Bouncy Castle支持MD4算法比较简单的办法是将Bouncy Castle导入项目中,并在初始化MessageDigest前通过Security类的addProvider()方法加载第三方加密组件包提供者,我们以BouncyCastleProvider为例,提供MD4算法支持。要使用MD4算法对数据做消息摘要,可参考如下代码:


import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

//省略

//加入BouncyCastleProvider支持

Security.addProvider(new BouncyCastleProvider());

//初始化MessageDigest

MessageDigest md=MessageDigest.getInstance("MD4");

//执行消息摘要

md.digest(data);

有关于第三方加密组件包Bouncy Castle的配置,读者朋友可阅读第4章相关内容。

下面给出MD4算法实现,如代码清单6-3所示。

代码清单6-3 MD4算法实现

import java.security.MessageDigest;

import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.bouncycastle.util.encoders.Hex;

/**

*MD4消息摘要组件

*@author梁栋

*@version 1.0

*@since 1.0

*/

public abstract class MD4Coder{

/**

*MD4消息摘要

*@param data待做摘要处理的数据

*@return byte[]消息摘要

*@throws Exception

*/

public static byte[]encodeMD4(byte[]data)throws Exception{

//加入BouncyCastleProvider支持

Security.addProvider(new BouncyCastleProvider());

//初始化MessageDigest

MessageDigest md=MessageDigest.getInstance("MD4");

//执行消息摘要

return md.digest(data);

}

/**

*MD4消息摘要

*@param data待做摘要处理的数据

*@return String消息摘要

*@throws Exception

*/

public static String encodeMD4Hex(byte[]data)throws Exception{

//执行消息摘要

byte[]b=encodeMD4(data);

//做十六进制编码处理

return new String(Hex.encode(b));

}

}


上述代码提供了MD4算法实现(encode MD4()方法)的同时,加入了十六进制转换方法实现(encodeMD4Hex()方法)。关于十六进制编码转换相关API请阅读第4章内容。

我们完全可以将Bouncy Castle的相关支持融入其他MD算法实现中,提供完整的MD系列算法,并加入十六进制转换实现。

对于上述方法实现给出对应的测试用例,见代码清单6-4。

代码清单6-4 MD4算法实现测试用例


import static org.junit.Assert.*;

import org.junit.Test;

/**

*MD4校验

*@author梁栋

*@version 1.0

*@since 1.0

*/

public class MD4CoderTest{

/**

*测试MD4

*@throws Exception

*/

@Test

public final void testEncodeMD4()throws Exception{

String str="MD4消息摘要";

//获得摘要信息

byte[]data1=MD4Coder.encodeMD4(str.getBytes());

byte[]data2=MD4Coder.encodeMD4(str.getBytes());

//校验

assertArrayEquals(data1,data2);

}

/**

*测试MD4Hex

*@throws Exception

*/

@Test

public final void testEncodeMD4Hex()throws Exception{

String str="MD4Hex消息摘要";

//获得摘要信息

String data1=MD4Coder.encodeMD4Hex(str.getBytes());

String data2=MD4Coder.encodeMD4Hex(str.getBytes());

System.err.println("原文:\t"+str);

System.err.println("MD4Hex-1:\t"+data1);

System.err.println("MD4Hex-2:\t"+data2);

//校验

assertEquals(data1,data2);

}


}


我们来关注一下testEncodeMD4Hex()方法在控制台上的输出结果,如下所示:


原文:MD4Hex消息摘要

MD4Hex-1:6d46694b818e0bab41eab6783749f963

MD4Hex-2:6d46694b818e0bab41eab6783749f963


我们获得的两个摘要值都是32位的十六进制字符串,并且是一致的。

如果把十六进制转换的实现融入到其他MD算法的实现中,那岂不是相当完美?

3.Commons Codec

对于Commons Codec,想必读者朋友已经不再陌生。它实现了Base64算法,还提供了用于消息摘要的工具类—DigestUtils类(它位于org.apache.commons.codec.digest包中,请读者朋友阅读第4章相关内容。)。DigestUtils类是对Sun提供的MessageDigest类的一次封装,提供了MD5和SHA系列消息摘要算法的实现。

我们通过代码清单6-5来了解如何通过Commons Codec实现MD5算法。

代码清单6-5 MD5算法实现


import org.apache.commons.codec.digest.DigestUtils;

/**

*MD5消息摘要组件

*@author梁栋

*@version 1.0

*@since 1.0

*/

public abstract class MD5Coder{

/**

*MD5消息摘要

*@param data待做摘要处理的数据

*@return byte[]消息摘要

*@throws Exception

*/

public static byte[]encodeMD5(String data)throws Exception{

//执行消息摘要

return DigestUtils.md5(data);

}

/**

*MD5消息摘要

*@param data待做摘要处理的数据

*@return byte[]消息摘要

*@throws Exception

*/

public static String encodeMD5Hex(String data)throws Exception{

//执行消息摘要

return DigestUtils.md5Hex(data);

}

}


虽然Commons Codec只是对Sun提供的MD5算法实现做了一次简单的包装,但着实为我们使用该算法提供了不小的便利。相应的测试用例见代码清单6-6。

代码清单6-6 MD5算法实现测试用例


import static org.junit.Assert.*;

import org.junit.Test;

/**

*MD5校验

*@author梁栋

*@version 1.0

*@since 1.0

*/

public class MD5CoderTest{

/**

*测试MD5

*@throws Exception

*/

@Test

public final void testEncodeMD5()throws Exception{

String str="MD5消息摘要";

//获得摘要信息

byte[]data1=MD5Coder.encodeMD5(str);

byte[]data2=MD5Coder.encodeMD5(str);

//校验

assertArrayEquals(data1,data2);

}

/**

*测试MD5Hex

*@throws Exception

*/

@Test

public final void testEncodeMD5Hex()throws Exception{

String str="MD5Hex消息摘要";

//获得摘要信息

String data1=MD5Coder.encodeMD5Hex(str);

String data2=MD5Coder.encodeMD5Hex(str);

System.err.println("原文:\t"+str);

System.err.println("MD5Hex-1:\t"+data1);

System.err.println("MD5Hex-2:\t"+data2);

//校验

assertEquals(data1,data2);

}

}


我们来关注一下testEncodeMD5Hex()方法在控制台上的输出结果,如下所示:


原文:MD5Hex消息摘要

MD5Hex-1:4effa30b56b61156bcd005eb9968cc9b

MD5Hex-2:4effa30b56b61156bcd005eb9968cc9b


对同一个消息做MD5Hex处理后,得到的摘要值都是32位的十六进制字符串,并且是一致的。

4.三种实现方式的差异

三种实现方式以Sun提供的实现为基础,在算法支持上和方法易用性上提供了更好的扩展与支持。

❑Sun

Sun提供的算法实现较为底层,支持MD2和MD5两种算法。但缺少了相应的进制转换实现,不能将其字节数组形式的摘要信息转为十六进制字符串,这多少有点不方便。

❑Bouncy Castle

Bouncy Castle是对Sun的友善补充,提供了对MD4算法的支持。支持多种形式的参数,支持十六进制字符串形式的摘要信息。

❑Commons Codec

如果仅仅需要实现MD5算法的话,使用Commons Codec完成消息摘要处理是一个不错的选择。它支持多种形式的参数,支持十六进制字符串形式的摘要信息。

综上所述,我们可以根据需要有机地结合两种实现方式,满足系统的需要。如果只要求MD5算法支持,Commons Codec是首选;若要支持MD2或MD4算法,或者要求在此基础上获得十六进制编码结果,就让Sun和Bouncy Castle联姻。