增强安全意识:MD5

前言

当今的网络环境越来越重视隐私数据的保护,以防信息被不法分子盗取滥用。在APP开发过程中,或多或少都会涉及到数据的安全存储和传输,比如:本地保存的用户登录态、网络请求中上传的用户敏感信息(密码和钱)等。作为一个进取的“搬砖”人,虽然不是专业的安全人员,但增强自己的安全意识也是有必要的,本文带大家捋一捋 MD5 算法。

MD5基本概念

MD5是什么

MD5(Message-Digest Algorithm)消息摘要算法,是一种被广泛使用的密码散列函数,可以产生一个128位的散列值,用于确保信息传输完整一致。MD5 由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代 MD4 算法。

MD5的长度

我们常见的 MD5 是一串由大小写字母和数字组成的简洁字符串,一般长度是32(少数是16)。其实,文本经过 MD5 算法计算后得出的是一个128位的二进制串,但二进制串阅读起来不方便,所以常常转成32位的16进制串。至于有16位出现,大概是去除了32位串的前8位和后8位,只保留中间16位串。

MD5的唯一性

MD5 值从理论上讲不是唯一的,即:一个原始数据只对应一个 MD5 值;但一个 MD5 值可能对应了多个原始数据。毕竟,现在主流的 MD5 值只有128位,也就是说有2的128次方种原始数据,但现实世界中原始数据是无限的。尽管这是一个理论上的有限对无限,不过这个无限在现实生活中并不完全成立,因为一方面现实中原文的长度往往是有限的,另一方面目前想要发现两段原文对应同一个 MD5(专业的说这叫杂凑冲撞)值非常困难。因此就目前而言,不同原文对应了不同的 MD5 值,可以看成是“唯一”的。

MD5是否可破解

MD5 是一种有损压缩算法,是不可逆的,但不代表不可破解。现在针对 MD5 的攻击手段,主流的应该是彩虹表字典法,有兴趣的同学可以了解一下这种技术(确实很惊艳)。不过,对 MD5 的攻击是一个耗时耗力的大工程。

彩虹表-百度百科

https://baike.baidu.com/item/%E5%BD%A9%E8%99%B9%E8%A1%A8/689313

彩虹表-知乎回答

https://www.zhihu.com/question/19790488

MD5的应用

1.一致性验证:对一段信息或者文件产生信息摘要,以防止被篡改。比如:我们在下载软件的时候通常都有一个 md5 值校验。

2.数字签名:对一段字符串产生指纹,也是防止被篡改。比如:第三方认证机构,用 MD5 来防止作者抵赖。

3.安全访问认证:典型的是密码存储,系统不用存储用户的密码明文,只需存储对应的 MD5 值即可,降低了暴露的风险。

MD5算法原理

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

640?wx_fmt=png

具体的算法过程,可以分解为:加工原始数据设置初始值循环运算拼接运算值四个部分。

加工原始数据

前面提到 MD5 是以512位为一个单位来进行处理的,而是否填充待运算的原始数据,不是看原始数据的长度是否是512的倍数,而是取决于对512求余的结果。如果余数不等于448,就需要填充数据使得原始数据对512求余的结果等于448。填充的方法是第一位填充1,其余位填充0。填充完后,信息的长度就是512*N+448。是不是很奇怪为什么是448?其实,剩余的64位要用来记录原文真正的长度(上图中有说明),所以,要把长度的二进制值补在最后。这样加工后的数据长度就变成了512*(N+1),刚好是512的倍数。

设置初始值

MD5 值长度为128位,按照32位分成四组,这四组的值是由四个初始值演变而来的。官方给的初始值为(物理存储:高位在高地址,地位在低地址,即67是低位在物理存储中是低地址):A=(01234567)16,B=(89ABCDEF)16,C=(FEDCBA98)16,D=(76543210)16。放在程序中就是A=0x67452301,B=0xEFCDAB89,C=0x98BADCFE,D=0x10325476(大端序,高位在低地址,即67在低地址)。

循环运算

首先看循环的次数。假设原始数据长度是L,以512位为单位,则总共要进行 L/512 次主循环。而每个主循环都处理512位数据,这些数据又可以分成16个小组,每组32位,每个主循环都要进行4轮(MD5规定)运算,也就是说每个主循环都有 16 * 4 = 64 次子循环

640?wx_fmt=png

其中:

1.绿色的F:代表非线性函数,官方给出了四种函数:

2.红色的田:代表加法运算。

3.Mi:前面说过,512位数据被分成了16个小组,每组32位,这些组就被命名为M0~M15。在主循环的64次子循环中,每16次循环,都会交替使用M0到M15。

4.Ki:常量,在64次子循环中,每次用到的常量都不一样。这些常量是4294967296*abs( sin(i) )的整数部分,i 取值从1到64,单位是弧度。(4294967296=2^(32))数据如下:

   /**	
    * 常量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};

5.黄色的<<<s:表示左移,s也是常量,数据如下:

   /*	
    * 向左位移数,计算方法未知	
    */	
    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};

一次子循环后,新的ABCD就产生了,归纳如下:

  • 新A = 原D

  • 新B = B+((A+F(B,C,D)+Mj+Ki)<<

  • 新C = 原B

  • 新D = 原A

每次计算的新值都会作为下次循环的输入。其中,4轮的运算公式总结下:

FF(a,b,c,d,Mj,s,ti)表示a=b+((a+F(b,c,d)+Mj+Ki)<<<s)	
GG(a,b,c,d,Mj,s,ti)表示a=b+((a+G(b,c,d)+Mj+Ki)<<<s)	
HH(a,b,c,d,Mj,s,ti)表示a=b+((a+H(b,c,d)+Mj+Ki)<<<s)	
II(a,b,c,d,Mj,s,ti)表示a=b+((a+I(b,c,d)+Mj+Ki)<<<s)

详细的4轮(64次)计算如下:

//  第一轮	
FF(a ,b ,c ,d ,M0 ,7 ,0xd76aa478 )	
FF(d ,a ,b ,c ,M1 ,12 ,0xe8c7b756 )	
FF(c ,d ,a ,b ,M2 ,17 ,0x242070db )	
FF(b ,c ,d ,a ,M3 ,22 ,0xc1bdceee )	
FF(a ,b ,c ,d ,M4 ,7 ,0xf57c0faf )	
FF(d ,a ,b ,c ,M5 ,12 ,0x4787c62a )	
FF(c ,d ,a ,b ,M6 ,17 ,0xa8304613 )	
FF(b ,c ,d ,a ,M7 ,22 ,0xfd469501)	
FF(a ,b ,c ,d ,M8 ,7 ,0x698098d8 )	
FF(d ,a ,b ,c ,M9 ,12 ,0x8b44f7af )	
FF(c ,d ,a ,b ,M10 ,17 ,0xffff5bb1 )	
FF(b ,c ,d ,a ,M11 ,22 ,0x895cd7be )	
FF(a ,b ,c ,d ,M12 ,7 ,0x6b901122 )	
FF(d ,a ,b ,c ,M13 ,12 ,0xfd987193 )	
FF(c ,d ,a ,b ,M14 ,17 ,0xa679438e )	
FF(b ,c ,d ,a ,M15 ,22 ,0x49b40821 )	
// 第二轮	
GG(a ,b ,c ,d ,M1 ,5 ,0xf61e2562 )	
GG(d ,a ,b ,c ,M6 ,9 ,0xc040b340 )	
GG(c ,d ,a ,b ,M11 ,14 ,0x265e5a51 )	
GG(b ,c ,d ,a ,M0 ,20 ,0xe9b6c7aa )	
GG(a ,b ,c ,d ,M5 ,5 ,0xd62f105d )	
GG(d ,a ,b ,c ,M10 ,9 ,0x02441453 )	
GG(c ,d ,a ,b ,M15 ,14 ,0xd8a1e681 )	
GG(b ,c ,d ,a ,M4 ,20 ,0xe7d3fbc8 )	
GG(a ,b ,c ,d ,M9 ,5 ,0x21e1cde6 )	
GG(d ,a ,b ,c ,M14 ,9 ,0xc33707d6 )	
GG(c ,d ,a ,b ,M3 ,14 ,0xf4d50d87 )	
GG(b ,c ,d ,a ,M8 ,20 ,0x455a14ed )	
GG(a ,b ,c ,d ,M13 ,5 ,0xa9e3e905 )	
GG(d ,a ,b ,c ,M2 ,9 ,0xfcefa3f8 )	
GG(c ,d ,a ,b ,M7 ,14 ,0x676f02d9 )	
GG(b ,c ,d ,a ,M12 ,20 ,0x8d2a4c8a )	
// 第三轮	
HH(a ,b ,c ,d ,M5 ,4 ,0xfffa3942 )	
HH(d ,a ,b ,c ,M8 ,11 ,0x8771f681 )	
HH(c ,d ,a ,b ,M11 ,16 ,0x6d9d6122 )	
HH(b ,c ,d ,a ,M14 ,23 ,0xfde5380c )	
HH(a ,b ,c ,d ,M1 ,4 ,0xa4beea44 )	
HH(d ,a ,b ,c ,M4 ,11 ,0x4bdecfa9 )	
HH(c ,d ,a ,b ,M7 ,16 ,0xf6bb4b60 )	
HH(b ,c ,d ,a ,M10 ,23 ,0xbebfbc70 )	
HH(a ,b ,c ,d ,M13 ,4 ,0x289b7ec6 )	
HH(d ,a ,b ,c ,M0 ,11 ,0xeaa127fa )	
HH(c ,d ,a ,b ,M3 ,16 ,0xd4ef3085 )	
HH(b ,c ,d ,a ,M6 ,23 ,0x04881d05 )	
HH(a ,b ,c ,d ,M9 ,4 ,0xd9d4d039 )	
HH(d ,a ,b ,c ,M12 ,11 ,0xe6db99e5 )	
HH(c ,d ,a ,b ,M15 ,16 ,0x1fa27cf8 )	
HH(b ,c ,d ,a ,M2 ,23 ,0xc4ac5665 )	
// 第四轮	
II(a ,b ,c ,d ,M0 ,6 ,0xf4292244 )	
II(d ,a ,b ,c ,M7 ,10 ,0x432aff97 )	
II(c ,d ,a ,b ,M14 ,15 ,0xab9423a7 )	
II(b ,c ,d ,a ,M5 ,21 ,0xfc93a039 )	
II(a ,b ,c ,d ,M12 ,6 ,0x655b59c3 )	
II(d ,a ,b ,c ,M3 ,10 ,0x8f0ccc92 )	
II(c ,d ,a ,b ,M10 ,15 ,0xffeff47d )	
II(b ,c ,d ,a ,M1 ,21 ,0x85845dd1 )	
II(a ,b ,c ,d ,M8 ,6 ,0x6fa87e4f )	
II(d ,a ,b ,c ,M15 ,10 ,0xfe2ce6e0 )	
II(c ,d ,a ,b ,M6 ,15 ,0xa3014314 )	
II(b ,c ,d ,a ,M13 ,21 ,0x4e0811a1 )	
II(a ,b ,c ,d ,M4 ,6 ,0xf7537e82 )	
II(d ,a ,b ,c ,M11 ,10 ,0xbd3af235 )	
II(c ,d ,a ,b ,M2 ,15 ,0x2ad7d2bb )	
II(b ,c ,d ,a ,M9 ,21 ,0xeb86d391 )

所有这些完成之后,将a、b、c、d分别再加上A、B、C、D。即a = a + A,b = b + B,c = c + C,d = d + D ,然后用下一分组数据继续运行以上算法。

拼接运算值

这一步就很简单了,将经过上述循环计算出来的四个32位值级联即可。一般都会转成16进制字符串(全小写),这样原始数据的 MD5 值就计算出来了。

MD5的Java实现

根据上文的描述,应该就能手动实现 MD5 算法了,可以参考百度百科里的写法。这里直接使用Java提供的MessageDigest类来完成 MD5 的计算。 MessageDigest提供了几个摘要算法:MD5、SHA-1、SHA-256等(Android包中进行了丰富,如 SHA-224)。MessageDigest主要关注 getInstanceupdatedigest这三个方法。

// 根据传入的algorithm,使用不同的算法实现,如:MD5、SHA-1	
public static MessageDigest getInstance(String algorithm)	
// 填充数据	
public void update(byte[] input)	
// 计算值,只能调用一次	
public byte[] digest()

以下是 MD5 的实现方法:

    /**	
     * 计算MD5	
     *	
     * @param str 待计算字符串	
     * @return 32位小写md5字符串	
     */	
    public static String getMD5ForStr(String str) {	
        if (!TextUtils.isEmpty(str)) {	
            try {	
                MessageDigest md5 = MessageDigest.getInstance(ALGORITHM_MD5);	
                md5.update(str.getBytes());	
                byte[] md5Bytes = md5.digest(); // 16字节,128位	
                return toHex(md5Bytes);	
            } catch (NoSuchAlgorithmException e) {	
                e.printStackTrace();	
            }	
        } else {	
            throw new IllegalArgumentException("the str is empty");	
        }	
        return "";	
    }	
    /**	
     * 计算流的MD5	
     * @param inputStream 文件流	
     * @return 32位小写md5字符串	
     */	
    public static String getMD5ForFile(InputStream inputStream) {	
        DigestInputStream dis = null;	
        byte[] buffer = new byte[1024];	
        try {	
            MessageDigest md5 = MessageDigest.getInstance(ALGORITHM_MD5);	
            dis = new DigestInputStream(inputStream, md5);	
            while (dis.read(buffer) > 0); // 相当于MessageDigest的update	
            md5 = dis.getMessageDigest();	
            return toHex(md5.digest());	
        } catch (Exception e) {	
            e.printStackTrace();	
        } finally {	
            try {	
                if (dis != null) {	
                    dis.close();	
                }	
            } catch (IOException e) {	
                e.printStackTrace();	
            }	
        }	
        return "";	
    }

举个栗子:Hello MD5 in Java和包含该串的README.txt文件进行 MD5 计算,结果如下:

640?wx_fmt=png

想看源码的同学请移步:

zjxstar的GitHub

https://github.com/zjxstar/AndroidSamples/tree/master/SecuritySample

参考资料

MD5 加密算法详细介绍:

https://blog.csdn.net/ling_du/article/details/51452091

MD5算法解析

https://blog.csdn.net/chensi16114/article/details/52116262

MD5算法原理及其实现

https://blog.csdn.net/u012611878/article/details/54000607

漫画:什么是MD5算法?

https://juejin.im/entry/59cf56a26fb9a00a4a4ceb64

MD5百度百科

https://baike.baidu.com/item/MD5

--END--

识别二维码,关注我们

640?wx_fmt=png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值