消息摘要算法总结与实践

序言

消息摘要算法平常使用的频率很高,经常我们用它来验证数据是否被篡改。 还有验证网络传输文件时,文件是否被篡改等等。

消息摘要算法有哪些

消息摘要算法主要分为三类: MD 、 SHA 、 MAC

  • MD(Message Digest) : 消息摘要
  • SHA(Secure Hash Algorithm) : 安全散列
  • MAC(Message Authentication Code) : 消息认证码

主要用途 : 验证消息的完整性

主要特性:

  • 输入的消息不受限制,但是输出的消息(经过消息摘要算法后的消息)固定;
  • 如果输入的消息不同,输出的消息也肯定不同;
  • 消息摘要算法是不可逆的; 即不能从摘要后的消息解析出任何源信息相关的信息

MD(Message Digest) : 消息摘要

MD系列算法一般有: MD2 、 MD4 、 MD5, 数字的值越大代表算法的版本越高,安全就越强。 即 MD5 > MD4 > MD2 。 自然破解难度MD5最高,平常用的最多的也是MD5算法。

MD系列算法输出信息的摘要长度都是固定的128bit ,即16字节;

因为JDK不支持MD4算法, 我们需引用第三方的依赖。在 pom.xml下加入这段才能使用MD4


  org.bouncycastle
  bcprov-jdk15on
  1.62

代码示例 :

/**
* @program
* @Desc MD消息摘要算法 测试
* @Author 游戏人日常
* @CreateTime 2019/07/09--15:14
*/
public class MDTest {

    private static final String src = "helloWorld";

    public static void main(String[] args) {
        jdkMD5();
        jdkMD2();
        bcMD4();
        jdkAndBcMd4();
    }

    //JDK 实现
    public static void jdkMD5() {
        try {
            //得到消息摘要对象
            MessageDigest md = MessageDigest.getInstance("MD5");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    //JDK 实现
    public static void jdkMD2() {
        try {
            //得到消息摘要对象
            MessageDigest md = MessageDigest.getInstance("MD2");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    //结合JDK 来实现
    public static void jdkAndBcMd4() {
        try {
            //加入BouncyCastle的支持 也可以不通过代码,需在Java/jdk1.8.0_181/jre/lib/security/java.security加入
            Security.addProvider(new BouncyCastleProvider());
            // 得到MD 消息摘要对象
            MessageDigest md = MessageDigest.getInstance("MD4");
            //对src 字符串进行消息摘要
            byte[] mD4Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(mD4Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    //通过 第三方 bouncy castle 来 实现
    public static void bcMD4() {
        //创建 MD4Digest 对象
        Digest md4Digest = new MD4Digest();
        //更新消息摘要 通过src字节数组
        md4Digest.update(src.getBytes(), 0, src.getBytes().length);
        //初始化字节数组大小
        byte[] mD4Bytes = new byte[md4Digest.getDigestSize()];
        //关闭摘要,然后得到摘要后的值
        md4Digest.doFinal(mD4Bytes, 0);
        //对得到的字节数组 转为16进制
        String result = new BigInteger(mD4Bytes).toString(16);
        System.out.println(result);
    }
}

SHA(Secure Hash Algorithm) : 安全散列

SHA系列算法从版本上一般分为两类: SHA-1 和 SHA-2

SHA-1就是我们常见的SHA1 , SHA-2主要有SHA224 、 SHA256 、 SHA384 、 SHA512等等,不同的数字代表输出消息的位数, 与MD系列算法一样,数字高表示算法的安全性越高,但不同于MD系列算法的是,SHA系列算法输出的信息摘要长度不一样。

  • SHA1 摘要算法后,输出的信息为160bit, 即20字节;
  • SHA224 摘要算法后,输出的信息为224bit, 即28字节;
  • SHA256 摘要算法后,输出的信息为256bit, 即32字节;
  • SHA384 摘要算法后,输出的信息为384bit, 即48字节;
  • SHA512 摘要算法后,输出的信息为512bit, 即64字节;

代码示例:

**
 * @program
 * @Desc  SHA 测试
 * @Author 游戏人日常
 * @CreateTime 2019/07/09--17:44
 */
public class SHATest {

    public static final String src="helloWorld";

    public static void  main(String [] args){
        jdkSHA1();
        bcSHA224();
        jdkSHA256();
        jdkSHA384();
        jdkSHA512();
    }

    //JDK实现
    public static void jdkSHA1(){
        try {
            //得到消息摘要对象
            MessageDigest md=MessageDigest.getInstance("SHA");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    // 通过 第三方 bouncy castle 来 实现
    public static void bcSHA224(){
        //创建 MD4Digest 对象
        Digest digest = new SHA224Digest();
        //更新消息摘要 通过src字节数组
        digest.update(src.getBytes(), 0, src.getBytes().length);
        //初始化字节数组大小
        byte[] mD4Bytes = new byte[digest.getDigestSize()];
        //关闭摘要,然后返回摘要后的值
        digest.doFinal(mD4Bytes, 0);
        //对得到的字节数组 转为16进制
        String result = new BigInteger(mD4Bytes).toString(16);
        System.out.println(result);
    }
    //JDK实现
    public static void jdkSHA256(){
        try {
            //得到消息摘要对象
            MessageDigest md=MessageDigest.getInstance("SHA-256");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    //JDK实现
    public static void jdkSHA384(){
        try {
            //得到消息摘要对象
            MessageDigest md=MessageDigest.getInstance("SHA-384");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    //JDK实现
    public static void jdkSHA512(){
        try {
            //得到消息摘要对象
            MessageDigest md=MessageDigest.getInstance("SHA-512");
            //对src 字符串进行消息摘要
            byte[] md5Bytes = md.digest(src.getBytes());
            //对得到的字节数组 转为16进制
            String result = new BigInteger(md5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

MAC(Message Authentication Code) : 消息认证码

MAC算法不同于MD和SHA,因为在MD和SHA算法基础上添加了密钥。 所以安全性相比MD和SHA系列更高。
MD系列:HmacMD2 、HmacMD4 、HmacMD5
SHA系列: HmacSHA1 、 HmacSHA224 、HmacSHA256 、HmacSHA384 、HmacSHA512
摘要后的长度跟MD和SHA系列一样。 如HmacMD5摘要长度跟MD5一样, 即160bit,20字节;

代码示例:

/**
 * @program
 * @Desc MAC 测试
 * @Author 游戏人日常
 * @CreateTime 2019/07/09--19:44
 */
public class MACTest {

    public static final String src="helloWorld";

    public static void  main(String [] args){
        jdkHmacMD5();
        HmacSHA1();
    }
    //JDK实现
    public static void jdkHmacMD5(){
        try {
            //获取key生成器对象
            KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacMD5");
            //获取密钥对象
            SecretKey secretKey=keyGenerator.generateKey();
            //获取密钥
            //byte [] key=secretKey.getEncoded();
            //可以自己设置密钥的来源, 必须是16进制, 长度是2的倍数
            byte [] key=Hex.decode("123456789abcde");

            //还原密钥  第二个参数是算法名称
            SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacMD5");
            //获取Mac对象
            Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
            //初始化Mac
            mac.init(restoreSecretKey);
            //对src执行摘要
            byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
            //对摘要的信息字节数组 转为16进制
            String result = new BigInteger(hmacMD5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }
    //JDK实现
    public static void HmacSHA1(){
        try {
            //获取key生成器对象
            KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacSHA1");
            //获取密钥对象
            SecretKey secretKey=keyGenerator.generateKey();
            //获取密钥
            //byte [] key=secretKey.getEncoded();
            //可以自己设置密钥的来源, 必须是16进制, 长度是2的倍数
            byte [] key=Hex.decode("123456789abcde");

            //还原密钥  第二个参数是算法名称
            SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacSHA1");
            //获取Mac对象
            Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
            //初始化Mac
            mac.init(restoreSecretKey);
            //对src执行摘要
            byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
            //对摘要的信息字节数组 转为16进制
            String result = new BigInteger(hmacMD5Bytes).toString(16);
            System.out.println(result);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }
}
//其他的摘要实现一样,大致都差不多,就不实现了。

密钥不同,得到的摘要消息肯定不同。 密钥的来源可以自己来构建,但必须满足是16进制, 长度是2的倍数。

应用场景

  • 登陆注册时:用户注册时,我们平常会把关键的信息会进行摘要后,如:密码。 然后持久化在数据库中。 用户登陆时:我们就对密码进行摘要然后去数据库去查询。 一般采取的摘要算法是MD5。

注册登陆时序图如下:


消息摘要算法总结与实践_第1张图片
注册时序图
消息摘要算法总结与实践_第2张图片
登陆时序图

上面只是个简单的用户注册和登陆流程,为什么要对密码进行摘要,不进行摘要的话,一旦如果数据库中的数据泄露了,如: 用户名和密码, 别人就可以拿这个用户名和密码去登陆。 摘要的话,别人即使获取到用户名和密码, 也是登陆不成功。 但是现在的MD5被国人破解了。

  • 网络传输数据时: 有些安全性比较高的,会对HTTP/HTTPS请求中的参数进行摘要,然后把摘要信息一并传输。 请求中参数具体怎么摘要这个自己定。一般还会提供个key。

某个SDK的服务器文档:


消息摘要算法总结与实践_第3张图片
某SKD的服务器文档

我们可以看到这个是把secret+uid+psid+roleid+amount+type+htnonce 这些参数值进行摘要,生成的值和原来的各个参数来作为HTTP参数。 这个文档的参数是按照这个规则来进行摘要的,有的会用参数的key进行排序然后来进行摘要。 具体的规则还是客户端和服务器端进行商定。

  • 在Android领域,我们会比较apk中md5(指的是对Android签名文件的摘要。 对apk解压后的original\META-INF\XXX.RSA文件中),来比较这个apk是否被二次打包过。 其他的文件也是一样,来判断是否被修改过


    消息摘要算法总结与实践_第4张图片
    获取apk中的信息

这个工具源码: https://github.com/songshuilin/parseApk

总结

上面列出三种摘要算法系列: MD系列 、 SHA系列 、 MAC系列。 但是我们还会见到CRC系列,在WinRAR 、WinZIP等软件中。
一般情况下安全性 MAC > SHA > MD > CRC

消息摘要特点:

  • 变长输入、定长输出 : 即 不管输入的消息多长,计算出来的摘要的长度是固定的。
  • 输入不同,摘要不同。 输入相同,摘要相同 ;
  • 单向不可逆: 即 只能进行正向的摘要,而无法从摘要中恢复原来的任何信息。
  • “碰撞” 难找到: 好的摘要算法,很难找到碰撞,虽然碰撞是肯定存在。 即无法找到两个不同的消息,但是他们的摘要相同。
消息摘要算法总结与实践_第5张图片
一个游戏技术人,分享Java、Python、H5、Android 等知识。同时会分享游戏行业日常。

你可能感兴趣的:(消息摘要算法总结与实践)