全称:message-digest algorithm 5
翻译过来就是:信息 摘要 算法 5
1.特点
-
1.长度固定:
不管多长的字符串,加密后长度都是一样长
作用:方便平时信息的统计和管理 -
2.易计算:
字符串和文件加密的过程是容易的.
作用: 开发者很容易理解和做出加密工具 -
3.细微性
一个文件,不管多大,小到几k,大到几G,你只要改变里面某个字符,那么都会导致MD5值改变.
作用:很多软件和应用在网站提供下载资源,其中包含了对文件的MD5码,用户下载后只需要用工具测一下下载好的文件,通过对比就知道该文件是否有过更改变动. -
4.不可逆性
你明明知道密文和加密方式,你却无法反向计算出原密码.
作用:基于这个特点,很多安全的加密方式都是用到.大大提高了数据的安全性
2.后续讲解
-
关于撞库破解:
这是概率极低的破解方法,原理就是:
1.建立一个大型的数据库,把日常的各个语句,通过MD5加密成为密文,不断的积累大量的句子,放在一个庞大的数据库里.
2.比如一个人拿到了别人的密文,想去查询真实的密码,就需要那这个密文去到提供这个数据库的公司网站去查询.
这就是撞库的概念.
3.关于MD5加盐:
比如我的银行密码是”12345”
1.得到的MD5是:827ccb0eea8a706c4c34a16891f84e7b
2.一个人截取到这个密文,那么通过撞库肯定容易撞出12345.
3.我们要做的就是加盐,银行密码还是”12345”,然后我把银行密码加上我特定的字符串才计算MD5
所以密码还是那个密码,但是变成求”12345密码加密987”的MD5值,然后再得到MD5,那么这个MD5起码可以确认那个数据库不会有.
说了那么多我们开始我们的MD5工具的制作
我们一般加密都是加密字符串或者文件,所以我们的工具就有加密字符串和文件的两种方法,两个方法同名,通过重载完成
1.加密字符串
逻辑思维:
-
1.获取信息摘要对象:md5
通过信息摘要单例的构造函数获取:
MessageDigest md5 = MessageDigest.getInstance("MD5");
-
2.信息摘要对象是对字节数组进行摘要的,所以先获取字符串的字节数组.
byte[] bytes = str.getBytes();
-
3.信息摘要对象对字节数组进行摘要,得到摘要字节数组:
byte[] digest = md5.digest(bytes);
-
4.把摘要数组中的每一个字节转换成16进制,并拼在一起就得到了MD5值.
(PS,有些转换过来得到的是前面有6个f的情况,如:ffffff82,这是因为前面有6组4个1,所以提前把这6组1111先变成0就好了,然后再转16进制就没有f了)
(其实也可以在后面续把f去掉)
2.加密文件
方法传入的是文件对象 : file
-
1.因为是文件不是方法,所以不是像刚才那样通过摘要获取字符串.
-
2.使用到另一个方法即可:就是信息摘要对象更新:md5.update(byte[] input)方法,用法是通过读取流,不断的更新从流中读到的”信息数组”.
-
3.然后通过”信息摘要对象”获取摘要,不用参数:md5.digest(),此时返回的数组就已经是包含内容的摘要数组了
以下是详细代码:
-
public
class MD5Tool {
-
public
static
void main(
String[] args) throws
Exception {
-
/*--------------字符串--------------*/
-
String str =
"12345";
-
String md1 = getMD5(str);
-
System.out.println(md1);
//827ccb0eea8a706c4c34a16891f84e7b
-
-
/*--------------文件--------------*/
-
File file =
new File(
"D:\\1.mp3");
-
String md2 = getMD5(file);
-
System.out.println(md2);
//9068aaead9a5b75e6a54395d8183ec9
-
}
-
/**
-
* 逻辑:
-
*
-
* 1.获取md5对象,通过"信息摘要"获取实例构造("MD5").
-
* 2.md5对象对("字符串的"字节形式"-得到的数组)进行摘要",那么会返回一个"摘要的字节数组"
-
* 3.摘要字节数组中的"每个二进制值"字节形式,"转成十六进制形式",然后再把这些值给拼接起来,就是MD5值了
-
* (PS:为了便于阅读,把多余的fff去掉,并且单个字符前加个0)
-
*
-
*/
-
public
static
String getMD5(
String str) throws
Exception {
-
-
String MD5 =
"";
-
-
MessageDigest md5 = MessageDigest.getInstance(
"MD5");
-
byte[] bytes = str.getBytes();
-
byte[] digest = md5.digest(bytes);
-
-
for (
int i =
0; i < digest.length; i++) {
-
//摘要字节数组中各个字节的"十六进制"形式.
-
int j = digest[i];
-
j = j &
0x000000ff;
-
String s1 =
Integer.toHexString(j);
-
-
if (s1.length() ==
1) {
-
s1 =
"0" + s1;
-
}
-
MD5 += s1;
-
}
-
return MD5;
-
}
-
//重载,所以用户传入"字符串"或者"文件"都可以解决.
-
-
/**
-
* 处理逻辑:
-
* 1.现在传入的是"文件",不是字符串
-
* 2.所以信息摘要对象.进行摘要得到数组不能像上面获得:md5.digest(bytes),因为不是str.getBytes得到bytes
-
* 3.其实还是通过mdt.digest();获取到字节数组,但是前期必须要有一个方法必须是md5.update(),即"信息摘要对象"要先更新
-
* 4."信息摘要更新"里面有(byte[] input),说明是读取流获取到的数组,所以我们就用这个方法.
-
* 5.所以最终的逻辑就是:
-
*
-
* 1.获取文件的读取流
-
* 2.不停的读取流中的"内容"放入字符串,放一部分就"更新"一部分.直到全部完毕
-
* 3.然后调用md5.digest();就会得到有内容的字节数组,剩下的就和上边一样了.
-
*/
-
public
static
String getMD5(File file) throws
Exception {
-
String MD5 =
"";
-
-
MessageDigest md5 = MessageDigest.getInstance(
"MD5");
-
FileInputStream fis =
new FileInputStream(file);
-
-
byte[] bytes =
new byte[
1024 *
5];
-
-
int len =
-1;
-
while ((len=fis.read(bytes))!=
-1) {
-
//一部分一部分更新
-
md5.update(bytes,
0, len);
-
}
-
byte[] digest = md5.digest();
-
for (
int i =
0; i <digest.length; i++) {
-
int n = digest[i] &
0x000000ff;
-
String s =
Integer.toHexString(n);
-
-
MD5 += s;
-
}
-
return MD5;
-
}
-
}
MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要。
以下所描述的消息长度、填充数据都以位(Bit)为单位,字节序为小端字节。
算法原理
1、数据填充
对消息进行数据填充,使消息的长度对512取模得448,设消息长度为X,即满足X mod 512=448。根据此公式得出需要填充的数据长度。
填充方法:在消息后面进行填充,填充第一位为1,其余为0。
2、添加消息长度
在第一步结果之后再填充上原消息的长度,可用来进行的存储长度为64位。如果消息长度大于264,则只使用其低64位的值,即(消息长度 对 264取模)。
在此步骤进行完毕后,最终消息长度就是512的整数倍。
3、数据处理
准备需要用到的数据:
- 4个常数: A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;
- 4个函数:F(X,Y,Z)=(X & Y) | ((~X) & Z); G(X,Y,Z)=(X & Z) | (Y & (~Z)); H(X,Y,Z)=X ^ Y ^ Z; I(X,Y,Z)=Y ^ (X | (~Z));
把消息分以512位为一分组进行处理,每一个分组进行4轮变换,以上面所说4个常数为起始变量进行计算,重新输出4个变量,以这4个变量再进行下一分组的运算,如果已经是最后一个分组,则这4个变量为最后的结果,即MD5值。
具体计算的实现较为复杂,建议查阅相关书籍,下面给出在C++上的实现代码。
代码实现
#MD5.h
-
1 #ifndef MD5H
-
2 #define MD5H
-
3 #include
<math.h>
-
4 #include
<Windows.h>
-
5
-
6 void ROL(unsigned int &s, unsigned short cx); //32位数循环左移实现函数
-
7 void ltob(unsigned int &i); //B\L互转,接受UINT类型
-
8 unsigned int* MD5(const char* mStr); //接口函数,并执行数据填充,计算MD5时调用此函数
-
9
-
10 #endif
#MD5.cpp
-
1 #include "MD5.h"
-
2
-
3 /*4组计算函数*/
-
4 inline unsigned int F(unsigned int X, unsigned int Y, unsigned int Z)
-
5 {
-
6 return (X & Y) | ((~X) & Z);
-
7 }
-
8 inline unsigned int G(unsigned int X, unsigned int Y, unsigned int Z)
-
9 {
-
10 return (X & Z) | (Y & (~Z));
-
11 }
-
12 inline unsigned int H(unsigned int X, unsigned int Y, unsigned int Z)
-
13 {
-
14 return X ^ Y ^ Z;
-
15 }
-
16 inline unsigned int I(unsigned int X, unsigned int Y, unsigned int Z)
-
17 {
-
18 return Y ^ (X | (~Z));
-
19 }
-
20 /*4组计算函数结束*/
-
21
-
22 /*32位数循环左移实现函数*/
-
23 void ROL(unsigned int &s, unsigned short cx)
-
24 {
-
25 if (cx > 32)cx %= 32;
-
26 s = (s
<< cx) | (s >> (32 - cx));
-
27 return;
-
28 }
-
29
-
30 /*B\L互转,接收UINT类型*/
-
31 void ltob(unsigned int &i)
-
32 {
-
33 unsigned int tmp = i;//保存副本
-
34 byte *psour = (byte*)&tmp, *pdes = (byte*)
&i;
-
35 pdes += 3;//调整指针,准备左右调转
-
36 for (short i = 3; i >= 0; --i)
-
37 {
-
38 CopyMemory(pdes - i, psour + i, 1);
-
39 }
-
40 return;
-
41 }
-
42
-
43 /*
-
44 MD5循环计算函数,label=第几轮循环(1
<=label<=4),lGroup数组=4个种子副本,M=数据(16组32位数指针)
-
45 种子数组排列方式
:
--A--D--C--B--,即
lGroup[
0]=
A;
lGroup[
1]=
D;
lGroup[
2]=
C;
lGroup[
3]=
B;
-
46 */
-
47
void
AccLoop(
unsigned
short
label,
unsigned
int *
lGroup,
void *
M)
-
48 {
-
49
unsigned
int *
i1, *
i2, *
i3, *
i4,
TAcc,
tmpi =
0; //定义
:4个指针;
T表累加器; 局部变量
-
50
typedef
unsigned
int(*
clac)(
unsigned
int
X,
unsigned
int
Y,
unsigned
int
Z); //定义函数类型
-
51
const
unsigned
int
rolarray[
4][
4] =
{
-
52 {
7,
12,
17,
22 },
-
53 {
5,
9,
14,
20 },
-
54 {
4,
11,
16,
23 },
-
55 {
6,
10,
15,
21 }
-
56 };//循环左移
-位数表
-
57
const
unsigned
short
mN[
4][
16] =
{
-
58 {
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15 },
-
59 {
1,
6,
11,
0,
5,
10,
15,
4,
9,
14,
3,
8,
13,
2,
7,
12 },
-
60 {
5,
8,
11,
14,
1,
4,
7,
10,
13,
0,
3,
6,
9,
12,
15,
2 },
-
61 {
0,
7,
14,
5,
12,
3,
10,
1,
8,
15,
6,
13,
4,
11,
2,
9 }
-
62 };//数据坐标表
-
63
const
unsigned
int *
pM =
static_cast<
unsigned
int*>(M);//转换类型为32位的Uint
-
64 TAcc = ((label - 1) * 16) + 1; //根据第几轮循环初始化T表累加器
-
65 clac clacArr[4] = { F, G, H, I }; //定义并初始化计算函数指针数组
-
66
-
67 /*一轮循环开始(16组->16次)*/
-
68 for (short i = 0; i
< 16; ++i)
-
69 {
-
70 /*进行指针自变换*/
-
71
i1 =
lGroup + ((
0 +
i) %
4);
-
72
i2 =
lGroup + ((
3 +
i) %
4);
-
73
i3 =
lGroup + ((
2 +
i) %
4);
-
74
i4 =
lGroup + ((
1 +
i) %
4);
-
75
-
76 /*第一步计算开始
:
A+
F(
B,
C,
D)+
M[
i]+
T[
i+
1] 注
:第一步中直接计算
T表*/
-
77
tmpi =
(*i1 +
clacArr[
label
-
1](*
i2, *
i3, *
i4) +
pM[(
mN[
label
-
1][
i])] + (
unsigned
int)(
0x100000000UL *
abs(
sin((
double)(
TAcc +
i)))));
-
78
ROL(
tmpi,
rolarray[
label
-
1][
i %
4]);//第二步
:循环左移
-
79 *
i1 =
*i2 +
tmpi;//第三步
:相加并赋值到种子
-
80 }
-
81
return;
-
82 }
-
83
-
84 /*接口函数,并执行数据填充*/
-
85
unsigned
int*
MD5(
const
char*
mStr)
-
86 {
-
87
unsigned
int
mLen =
strlen(mStr); //计算字符串长度
-
88
if (
mLen <
0)
return
0;
-
89
unsigned
int
FillSize =
448
- ((
mLen *
8) %
512); //计算需填充的
bit数
-
90
unsigned
int
FSbyte =
FillSize /
8; //以字节表示的填充数
-
91
unsigned
int
BuffLen =
mLen +
8 +
FSbyte; //缓冲区长度或者说填充后的长度
-
92
unsigned
char *
md5Buff =
new
unsigned
char[
BuffLen]; //分配缓冲区
-
93
CopyMemory(
md5Buff,
mStr,
mLen); //复制字符串到缓冲区
-
94
-
95 /*数据填充开始*/
-
96
md5Buff[
mLen] =
0x80; //第一个
bit填充
1
-
97
ZeroMemory(&
md5Buff[
mLen +
1],
FSbyte
-
1); //其它
bit填充
0,另一可用函数为
FillMemory
-
98
unsigned
long
long
lenBit =
mLen *
8ULL; //计算字符串长度,准备填充
-
99
CopyMemory(&
md5Buff[
mLen +
FSbyte], &
lenBit,
8); //填充长度
-
100 /*数据填充结束*/
-
101
-
102 /*运算开始*/
-
103
unsigned
int
LoopNumber =
BuffLen /
64; //以
16个字为一分组,计算分组数量
-
104
unsigned
int
A =
0x67452301,
B =
0x0EFCDAB89,
C =
0x98BADCFE,
D =
0x10325476;//初始4个种子,小端类型
-
105
unsigned
int *
lGroup =
new
unsigned
int[
4]{
A,
D,
C,
B}; //种子副本数组,并作为返回值返回
-
106
for (
unsigned
int
Bcount =
0;
Bcount <
LoopNumber; ++
Bcount) //分组大循环开始
-
107 {
-
108 /*进入
4次计算的小循环*/
-
109
for (
unsigned
short
Lcount =
0;
Lcount <
4;)
-
110 {
-
111
AccLoop(++
Lcount,
lGroup, &
md5Buff[
Bcount *
64]);
-
112 }
-
113 /*数据相加作为下一轮的种子或者最终输出*/
-
114
A =
(lGroup[0] +=
A);
-
115
B =
(lGroup[3] +=
B);
-
116
C =
(lGroup[2] +=
C);
-
117
D =
(lGroup[1] +=
D);
-
118 }
-
119 /*转换内存中的布局后才能正常显示*/
-
120
ltob(
lGroup[
0]);
-
121
ltob(
lGroup[
1]);
-
122
ltob(
lGroup[
2]);
-
123
ltob(
lGroup[
3]);
-
124
delete[]
md5Buff; //清除内存并返回
-
125
return
lGroup;
-
126 }
再给出调用实例(以win32控制台应用程序为例):
#main.cpp
-
1 #include
<iostream>
-
2 #include
<string.h>
-
3 #include
<stdlib.h>
-
4 #include "MD5.h"
-
5
-
6 int main(int argc, char **argv)
-
7 {
-
8 char tmpstr[256], buf[4][10];
-
9 std::cout
<< "请输入要加密的字符串:";
-
10
std::cin >> tmpstr;
-
11 unsigned int* tmpGroup = MD5(tmpstr);
-
12 sprintf_s(buf[0], "%8X", tmpGroup[0]);
-
13 sprintf_s(buf[1], "%8X", tmpGroup[3]);
-
14 sprintf_s(buf[2], "%8X", tmpGroup[2]);
-
15 sprintf_s(buf[3], "%8X", tmpGroup[1]);
-
16 std::cout
<<"MD5:"<< buf[0] << buf[1] << buf[2] << buf[3] << std::endl;
-
17
-
18
delete[]
tmpGroup;
-
19
return
0; //在此下断点才能看到输出的值
-
20 }
拓展
0xfffffff代表的含义:
-
0x:代表16进制;
-
一个f代表:4个1,即(1111);
-
所以0xffffffff代表:8组4个1
1111 - 1111 - 1111 - 1111 - 1111 - 1111 - 1111 - 1111
-
所以刚才的0xffffff82就是前面6组都是1,后面两组是
1111 - 1111 - 1111 - 1111 - 1111 - 1111 - 0111 - 0010
-
所以先与上0x000000ff,即
0000 - 0000 - 0000 - 0000 - 0000 - 0000 - 1111 - 1111
-
就得到了82了
上面的方法也可以写写成:
-
for (
int i =
0
; i < digest.length; i++) {
-
//摘要字节数组中各个字节的
"十六进制"形式.
-
String s1 = Integer.toHexString(
digest[i])
;
-
-
//如果是
8个长度的,把前面的
6个f去掉,只获取后面的
-
if (
s1.length() ==
8) {
-
s1 = s1.substring(
6)
;
-
}
-
if (
s1.length() ==
1) {
-
s1 =
"0" + s1
;
-
}
-
MD5 += s1
;
-
}
-