Java 加解密技术系列之 MD5

上一篇文章中,介绍了最基础的编码方式 — —
BASE64,也简单的提了一下编码的原理。这篇文章继续加解密的系列,当然也是介绍比较基础的加密方式 — — MD5,MD5 属于单向加密算法,是不可逆的加密方式,也就是说,采用了 MD5 加密方式加密之后,就不能对加密的结果进行解密,得到原有的字符串,这是不可以的。

背景

相信在我们的生活中,MD5 用到的还是很广泛的。在说 MD5 之前,首先来了解一下单向加密算法都有哪些。当然,MD5 是其中之一,除此之外还有,SHA,HMAC 等这几种算法。不过,今天这篇文章,我们只介绍 MD5,至于 SHA 和 HMAC 在后续的文章中会陆续的介绍。

正文

MD5,全称为“Message Digest Algorithm 5”,中文名“消息摘要算法第五版”,它是计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。严格来说,它是一种摘要算法,是确保信息完整性的。不过,在某种意义上来说,也可以算作一种加密算法。

MD5 算法具有很多特点:

  • 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
  • 容易计算:从原数据计算出MD5值很容易。
  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
  • 弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
  • 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。

MD5 的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。

MD5 其实在我们生活中是很常用的,似乎你并没有注意到,当你下载了一个镜像之后,你会发现下载页面还提供了一组 MD5 值,那么这组 MD5 值是用来做什么的呢?了解了 MD5 的作用之后,你就不难想到,MD5 是用来验证文件的一致性的,当你下载好镜像之后,你需要对该镜像做一次 MD5 的校验,得到的 MD5 值与下载页面提供的 MD5 值进行对比,以此来验证该镜像是否被篡改。

为什么 MD5 就可以进行一致性校验呢?

其实,MD5 就和人的指纹一样,每个人的指纹都是唯一的,而文件的 MD5 值也是唯一的。至于为什么会这样呢?下面我们看一下 MD5 的工作原理。

对 MD5 算法简要的叙述可以为:MD5 以 512 位分组来处理输入的信息,且每一分组又被划分为 16 个 32 位子分组,经过了一系列的处理后,算法的输出由四个 32 位分组组成,将这四个 32 位分组级联后将生成一个 128 位散列值。

总体流程如下图所示, 表示第 i 个分组,每次的运算都由前一轮的 128 位结果值和第 i 块 512 bit 值进行运算。

填充

在 MD5 算法中,首先需要对信息进行填充,使其位长对 512 求余的结果等于 448,并且填充必须进行,即使其位长对 512 求余的结果等于 448。因此,信息的位长(Bits Length)将被扩展至 N * 512 + 448,N 为一个非负整数,N 可以是零。

填充的方法如下:

1) 在信息的后面填充一个 1 和无数个 0,直到满足上面的条件时才停止用 0 对信息的填充。

2) 在这个结果后面附加一个以 64 位二进制表示的填充前信息长度(单位为Bit),如果二 进制表示的填充前信息长度超过 64 位,则取低 64 位。

经过这两步的处理,信息的位长 = N * 512 + 448 + 64 = (N + 1)* 512,即长度恰好是 512 的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。

初始化变量

初始的 128 位值为初试链接变量,这些参数用于第一轮的运算,以大端字节序来表示,他们分别为: A = 0x01234567,B = 0x89ABCDEF,C = 0xFEDCBA98,D = 0x76543210。

(每一个变量给出的数值是高字节存于内存低地址,低字节存于内存高地址,即大端字节序。在程序中变量 A、B、C、D 的值分别为0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476)

处理分组数据

每一分组的算法流程如下:

第一分组需要将上面四个链接变量复制到另外四个变量中:A 到 a,B 到 b,C 到 c,D 到 d。从第二分组开始的变量为上一分组的运算结果,即 A = a, B = b, C = c, D = d。

主循环有四轮,每轮循环都很相似。第一轮进行 16 次操作。每次操作对 a、b、c 和 d 中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上 a、b、c 或 d 中之一。最后用该结果取代 a、b、c
或 d 中之一。

输出

最后的输出是 a、b、c 和 d 的级联。

代码

这里提供一个 Java 版本的实现,不过需要说明的一点是,这个 Java 实现对于英文的 MD5 是没有问题的,但对于中文会有点问题,因此,推荐只作为学习为目的来参考。如果是项目中的生产需要,请选择 jdk 中自带的 MD5 加密函数。

<span style="font-family:Arial;font-size:12px;">package com.sica.md5.impl;

/**
 * Created by xiang.li on 2015/2/26.
 */
public class MD5 {
    /**
     * 单例
     */
    private static MD5 instance;

    /**
     * 四个链接变量
     */
    private final int A = 0x67452301;
    private final int B = 0xefcdab89;
    private final int C = 0x98badcfe;
    private final int D = 0x10325476;

    /**
     * ABCD的临时变量
     */
    private int Atemp;
    private int Btemp;
    private int Ctemp;
    private int Dtemp;

    /**
     * 常量ti
     * 公式:floor(abs(sin(i+1))×(2pow32)
     */
    private final int[] K = {
                0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
                0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8,
                0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193,
                0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51,
                0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
                0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905,
                0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681,
                0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60,
                0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
                0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244,
                0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
                0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314,
                0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
    };

    /**
     * 向左位移数,计算方法未知
     */
    private final int[] s = {
            7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7,
            12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
            4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
            15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
    };

    /**
     * 私有构造函数
     */
    private MD5() {

    }

    /**
     * 单例模式
     * @return
     */
    public static MD5 getInstance() {
        if (instance == null) {
            instance = new MD5();
        }
        return instance;
    }

    /**
     * 初始化函数
     */
    private void init() {
        Atemp = A;
        Btemp = B;
        Ctemp = C;
        Dtemp = D;
    }

    /**
     * 移动一定位数
     * @param a
     * @param s
     * @return
     */
    private int shift(int a, int s) {
        return (a << s) | (a >>> (32 - s)); // 右移的时候,高位一定要补零,而不是补充符号位
    }

    /**
     * 主循环
     * @param M
     */
    private void mainLoop(int[] M) {
        int F;
        int g;
        int a = Atemp;
        int b = Btemp;
        int c = Ctemp;
        int d = Dtemp;

        for (int i = 0; i < 64; i++) {
            if (i < 16) {
                F = (b & c) | ((~b) & d);
                g = i;
            }else if (i < 32) {
                F = (d & b) | ((~d) & c);
                g = (5 * i + 1) % 16;
            }else if (i < 48) {
                F = b ^ c ^ d;
                g = (3 * i + 5) % 16;
            } else {
                F = c ^ (b | (~d));
                g = (7 * i) % 16;
            }

            int tmp = d;
            d = c;
            c = b;
            b = b + shift(a + F + K[i] + M[g], s[i]);
            a = tmp;
        }

        Atemp += a;
        Btemp += b;
        Ctemp += c;
        Dtemp += d;
    }

    /**
     * 填充函数
     * 处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64)
     * 填充方式为先加一个0,其它位补零
     * 最后加上64位的原来长度
     * @param str
     * @return
     */
    private int[] add(String str) {
        int num = ((str.length() + 8) / 64) + 1;    // 以512位,64个字节为一组
        int[] strByte = new int[num * 16];  // 64/4=16,所以有16个整数

        for (int i = 0; i < num * 16; i++) {
            // 全部初始化为0
            strByte[i] = 0;
        }

        int j;
        for (j = 0; j < str.length(); j++) {
            strByte[j >> 2] |= str.charAt(j) << ((j % 4) * 8);  // 一个整数存储四个字节,小端序
        }
        strByte[j >> 2] |= 0x80 << ((j % 4) * 8);   // 尾部添加1

        // 添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位
        strByte[num * 16 - 2] = str.length() * 8;

        return strByte;
    }

    /**
     * 调用函数
     * @param source 原始字符串
     * @return
     */
    public String getMD5(String source) {
        // 初始化
        init();
        int[] strByte = add(source);
        for (int i = 0; i < strByte.length / 16; i += 16) {
            int[] num = new int[16];
            for (int j = 0; j < 16; j++) {
                num[j] = strByte[i * 16 + j];
            }
            mainLoop(num);
        }
        return changeHex(Atemp) + changeHex(Btemp) + changeHex(Ctemp) + changeHex(Dtemp);
    }

    /**
     * 整数变成16进制字符串
     * @param a 整数
     * @return
     */
    private String changeHex(int a) {
        String str="";
        String tmp = "";
        for(int i=0;i<4;i++) {
            tmp = Integer.toHexString(((a >> i * 8) % (1 << 8)) & 0xff);
            if (tmp.length() < 2) {
                tmp = "0" + tmp;
            }
            str += tmp;
        }
        return str;
    }

    /**
     * 测试方法
     * @param args
     */
    public static void main(String[] args) {
        String str = MD5.getInstance().getMD5("");
        String str1 = MD5.getInstance().getMD5("123");
        System.out.println(str);
        System.out.println("d41d8cd98f00b204e9800998ecf8427e");
        System.out.println(str1);
        System.out.println("202cb962ac59075b964b07152d234b70");
    }
}</span><span style="font-family:微软雅黑;font-size: 14px;">
</span>

结束语

或许你经常见到 MD5,但你从来没有注意过,到底什么才是 MD5。也或许你知道什么是 MD5,但或许你并不了解 MD5 是作何用的。那么,从今天起,从你读完这篇博客起,我相信,以后再见到 MD5 的时候,你肯定会对它印象深刻,再下载文件的时候,也会对它进行 MD5 的一致性校验。那么,我就可以说,我的这篇文章还是起到了一丁点的作用。

时间: 2024-10-27 21:02:52

Java 加解密技术系列之 MD5的相关文章

Java 加解密技术系列文章

Java 加解密技术系列之 总结 Java 加解密技术系列之 DH Java 加解密技术系列之 RSA Java 加解密技术系列之 PBE Java 加解密技术系列之 AES Java 加解密技术系列之 3DES Java 加解密技术系列之 DES Java 加解密技术系列之 HMAC Java 加解密技术系列之 SHA Java 加解密技术系列之 MD5 Java 加解密技术系列之 BASE64

5.Java 加解密技术系列之 DES

Java 加解密技术系列之 DES 序 背景 概念 基本原理 主要流程 分组模式 代码实现 结束语 序 前 几篇文章讲的都是单向加密算法,其中涉及到了 BASE64.MD5.SHA.HMAC 等几个比较常见的加解密算法.这篇文章,以及后面几篇,打算介绍几个对称加密算法,比如:DES.3DES(TripleDES).AES 等.那么,这篇文章主要是对 DES 大概讲一下. 背景 在 讨论 DES 之前,首先了解一下什么是对称加密算法吧.对于对称加密算法,他应用的时间比较早,技术相对来说比较成熟,在

7.java 加解密技术系列之 AES

java 加解密技术系列之 AES 序 概念 原理 应用 代码实现 结束语 序 这篇文章继续介绍对称加密算法,至于今天的主角,不用说,也是个厉害的角色 — — AES.AES 的出现,就是为了来替代原先的 DES 标准.现在来说,AES 的用途还是非常广泛的. 概念 AES, 全称为“Advanced Encryption Standard”,中文名“高级加密标准”,在密码学中又称 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准.AES 加密算法作为新一代的数据加密标准汇聚了强安

4.Java 加解密技术系列之 HMAC

Java 加解密技术系列之 HMAC 序 背景 正文 代码 结束语 序 上一篇文章中简单的介绍了第二种单向加密算法 — —SHA,同时也给出了 SHA-1 的 Java 代码.有这方面需求的童鞋可以去参考一下.今天这篇文章将要介绍第三种单向加密算法 — — HMAC,其实,这种加密算法并不是那么常用,最起码,在我写系列博客之前,我是没有听说过它的.当然,这并不是说 HMAC 不出名,肯定是我孤落寡闻了. 背景 之所以在单向加密算法中介绍 HMAC 这种“不常见的”算法,一是因为“没见过”,二是因

3.Java 加解密技术系列之 SHA

Java 加解密技术系列之 SHA 序 背景 正文 SHA-1 与 MD5 的比较 代码实现 结束语 序 上一篇文章中介绍了基本的单向加密算法 — — MD5,也大致的说了说它实现的原理.这篇文章继续之前提到的单向加密,主要讲的是 SHA,同 MD5 一样,SHA 同样也是一个系列,它包括 SHA-1,SHA-224,SHA-256,SHA-384,和 SHA-512 等几种算法.其中,SHA-1,SHA-224 和 SHA-256 适用于长度不超过 2^64 二进制位的消息.SHA-384 和

10.Java 加解密技术系列之 DH

Java 加解密技术系列之 DH 序 概念 原理 代码实现 结果 结束语 序 上一篇文章中简单的介绍了一种非对称加密算法 — — RSA,今天这篇文章,继续介绍另一种非对称加密算法 — — DH.当然,可能有很多人对这种加密算法并不是很熟悉,不过没关系,希望今天这篇文章能帮助你熟悉他. 原理 整个通信过程中g.g^a.g^b是公开的,但由于g.a.b都是整数,通过g和g^a得到a还是比较容易的,b也是如此,所以最终的“密钥”g^(a*b)还是可以被计算出来的.所以实际的过程还需要在基本原理上加入

8.Java 加解密技术系列之 PBE

Java 加解密技术系列之 PBE 序 概念 原理 代码实现 结束语 序 前 边的几篇文章,已经讲了几个对称加密的算法了,今天这篇文章再介绍最后一种对称加密算法 — — PBE,这种加密算法,对我的认知来说,并没有 DES.3DES.AES 那么流行,也不尽然,其实是我之前并没有这方面的需求,当然接触他的机会也就很少了,因此,可想而知,没听过显然在正常不过了. 概念 PBE,全称为“Password Base Encryption”,中文名“基于口令加密”,是一种基于密码的加密算法,其特点是使用

11.Java 加解密技术系列之 总结

Java 加解密技术系列之 总结 序 背景 分类 常用算法 原理 关于代码 结束语 序 上一篇文章中简单的介绍了第二种非对称加密算法 — — DH,这种算法也经常被叫做密钥交换协议,它主要是针对密钥的保护.同时,由于水平的限制,打算这个系列就到此为止了,这篇文章就算是一个总结吧,回顾一下这几个月来都写了些什么. 背景 其 实,在开始写这个系列之前,我对于 Java 的加解密也并不是那么了解.之所以要写这些文章,还主要是由于工作的原因.记得几个月以前,当时项目要做一个数字证书,证书的生成.存储.传

6. Java 加解密技术系列之 3DES

Java 加解密技术系列之 3DES 序 背景 概念 原理 代码实现 结束语 序 上一篇文章讲的是对称加密算法 — — DES,这篇文章打算在 DES 的基础上,继续多讲一点,也就是 3 重 DES — — Triple DES. 背景 至于 3DES 为什么会出现呢?其实,这个不难想到.由于 DES 是一种非常简便的加密算法,但是密钥长度比较短,计算量比较小,相对来说,比较容易被破解.因此,在 DES 的基础上,使用三重数据加密算法,对数据进行加密,这样来说,破解的概率就小了很多. 概念 3D