前言
当今的网络环境越来越重视隐私数据的保护,以防信息被不法分子盗取滥用。在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位散列值。
具体的算法过程,可以分解为:加工原始数据、设置初始值、循环运算、拼接运算值四个部分。
加工原始数据
前面提到 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 次子循环。
其中:
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
主要关注 getInstance
、 update
和 digest
这三个方法。
// 根据传入的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 计算,结果如下:
想看源码的同学请移步:
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--
识别二维码,关注我们