引入:

我们在http://supercharles888.blog.51cto.com/609344/1313864 中讲到了加密的几种方式,其中最简单的是单向加密,它的作用是用来校验数据的完整性。

如果大家玩过网游的话,都知道任何客户端或者补丁的下载,一般在下载处,都会提供一个MD5校验码,其实这个就是保证你下载完这个客户端或者补丁是完整没有被篡改过的。

密码学研究-消息摘要(MessageDigest)_第1张图片

当然了, 很多游戏客户端并不需要自己去算这个MD5校验码然后和官方给出的MD5的值进行比对,很多游戏工具都提供了MD5校验工具,比如我最喜欢的“魔兽世界”,它在工具包中就提供了MD5校验工具,你只要吧下载的客户端放到工具中校验下,它就自动会识别你这个客户端的完整性。这个很重要,因为大家都知道,现在的游戏客户端多数有可能被挂***,这些***会盗取你的账号信息,从而影响你的虚拟财产。这里我们的被下载的软件是广义的”消息",而被散列后的值是“消息摘要


透过现象看本质,我们知道,这个数据完整性的校验是由哈希函数保证的,又称“散列”,它有若干的特点可以保证完成我们的任务。

a. 它是正向不可逆的,你只能从原始值转为hash值,而无法从hash值倒推到原始值。

b.相同的原始内容被相同哈希函数哈希后其哈希值一定是相同的。

b.不同的原始内容被相同哈希函数哈希后其哈希值一定是不同的。(这个是相对的, 因为世界上的消息,文件是无限多的,但是定长的哈希值能表示的内容范围是有上限的,比如64位就最多能表示2的64次方,但是这种“碰撞”的概率极小极小,对于MD5来说,已经有人破解了这个算法,给出了找出碰撞的原始内容的快捷方法,有兴趣的同学可以参考相应的文献http://wenku.baidu.com/view/bfbcf17f5acfa1c7aa00ccf6.html


JDK中,也提供了MessageDigest(消息摘要)类来专门处理如何将一个消息hash成对应的消息摘要(hash值),因为JDK有许多不同的Provider,见我们上文http://supercharles888.blog.51cto.com/609344/1314058 ,所以对于消息摘要来说也有多种不同的算法,当然了,对于这个API的使用上,是完全一致的,而且虽然这个类名字叫消息摘要,其实它不仅可以处理文本类消息,还可以处理二进制文件作为原始内容(就像我们网络游戏的客户端或者补丁),我们这里就用编程的方式来演示下这个类的常见的使用。


实践:

为了应付各种情况,我们提供了一个工具类,它的方法1可以将某消息通过给定算法转为对应的消息摘要,方法2可以将给定的二进制文件通过给定算法转为对应的消息摘要。我们看下实现:

package com.charles.securitystudy;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
/**
 *
 * Description:提供一组工具类来方便我们计算消息摘要
 *
 * @author charles.wang
 * @created Oct 24, 2013 10:21:19 AM
 *
 */
public class MessageDigestUtil {
    /**
     * 为指定的字符串产生消息摘要
     *
     * @param mesage
     * @return
     */
    public static String genMessageDigestForString(String message, String algorithm) {
        // 原始的信息,将其转为原始字节数组
        byte[] input = message.getBytes();
        try {
            // 用指定的算法来初始化消息摘要。
            MessageDigest md = MessageDigest.getInstance(algorithm);
            // 返回消息摘要的长度
            int digestLength = md.getDigestLength();
            System.out.println("消息摘要长度为:" + digestLength);
            // 返回消息摘要的提供者
            Provider provider = md.getProvider();
            System.out.println("消息摘要的提供者为:" + provider.getName());
            // 更新消息摘要,从而让消息摘要变为对应原始信息的消息摘要
            md.update(input);
            // 完成更新消息摘要动作,从消息摘要中返回散列后的字节数组
            byte[] values = md.digest();
            return (byte2hex(values));
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            System.out.println("不存在此消息摘要算法");
            return null;
        }
    }
    /**
     * 为二进制文件流产生消息摘要
     *
     * @param args
     * @throws Exception
     */
    public static String genMessageDigestForBinaryFile(String fileName, String algorithm) {
        try {
            // 用指定的算法来初始化消息摘要
            MessageDigest md = MessageDigest.getInstance(algorithm);
            FileInputStream fis = new FileInputStream(fileName);
            DigestInputStream dis = new DigestInputStream(fis, md);
            // 计算摘要
            md = dis.getMessageDigest();
            // 完成更新消息摘要动作,从消息摘要中返回散列后的字节数组
            byte[] values = md.digest();
            return (byte2hex(values));
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            System.out.println("不存在此消息摘要算法");
            return null;
        } catch (FileNotFoundException ffe) {
            ffe.printStackTrace();
            System.out.println("文件不存在");
            return null;
        }
    }
    /**
     * 将二进制转为字符串的形式
     *
     * @param b
     * @return
     */
    protected static String byte2hex(byte[] b) // 二行制转字符串
    {
        // 最终要转化的16进制字符串
        StringBuilder hexString = new StringBuilder();
        // 处理每个转化的当前字符串
        String tmpStr = "";
        for (int n = 0; n < b.length; n++) {
            // 将二进制转为16进制
            tmpStr = (Integer.toHexString(b[n] & 0XFF));
            // 如果当前转成的字符串只有一位长度的话,则前面补0,然后加上当前转换值
            if (tmpStr.length() == 1) {
                hexString.append("0").append(tmpStr);
            }
            // 否则,,则直接将当前转换值tmpStr附加在hexString后面
            else
                hexString.append(tmpStr);
            // 如果没有到byte[]的尾部,则中间用冒号分开,最后一个后面不用加冒号
            if (n < b.length - 1)
                hexString.append(":");
        }
        return hexString.toString().toUpperCase();
    }
}



然后我们用一个测试类来做实验:

package com.charles.securitystudy;
/**
 *
 * Description: 演示类
 *
 * @author charles.wang
 * @created Oct 23, 2013 5:44:18 PM
 *
 */
public class MessageDigestDemo {
                                                                                    
                                                                                    
                                                                                    
    public static void main(String [] args) throws Exception{
                                                                                        
        //演示为指定字符串产生消息摘要
        System.out.println("演示为指定字符串产生消息摘要");
        String givenString="abcdedf";
        String digestAlgorithm="SHA";
        System.out.println("原始字符串为:"+givenString+","+"使用的消息摘要算法为:"+digestAlgorithm);
        System.out.println("产生的消息摘要为:"+MessageDigestUtil.genMessageDigestForString(givenString,digestAlgorithm));
        System.out.println();
                                                                                        
        //演示为指定二进制文件产生消息摘要
        System.out.println("演示为指定二进制文件产生消息摘要");
        String fileName="travel.jpg";
        System.out.println("原始文件为:"+fileName+","+"使用的消息摘要算法为:"+digestAlgorithm);
        System.out.println("产生的消息摘要为:"+MessageDigestUtil.genMessageDigestForBinaryFile(fileName,digestAlgorithm));
                                                                                        
    }
                                                                                    
                                                                                    
                                                                                    
}



最后我们来看下运行结果:

密码学研究-消息摘要(MessageDigest)_第2张图片


这说明,我们提供的方法是正确的,大家可以拿来随便用哦。