MD5加密算法原理及实现

全称: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(),此时返回的数组就已经是包含内容的摘要数组


以下是详细代码:


   
   
  1. public class MD5Tool {
  2. public static void main( String[] args) throws Exception {
  3. /*--------------字符串--------------*/
  4. String str = "12345";
  5. String md1 = getMD5(str);
  6. System.out.println(md1); //827ccb0eea8a706c4c34a16891f84e7b
  7. /*--------------文件--------------*/
  8. File file = new File( "D:\\1.mp3");
  9. String md2 = getMD5(file);
  10. System.out.println(md2); //9068aaead9a5b75e6a54395d8183ec9
  11. }
  12. /**
  13. * 逻辑:
  14. *
  15. * 1.获取md5对象,通过"信息摘要"获取实例构造("MD5").
  16. * 2.md5对象对("字符串的"字节形式"-得到的数组)进行摘要",那么会返回一个"摘要的字节数组"
  17. * 3.摘要字节数组中的"每个二进制值"字节形式,"转成十六进制形式",然后再把这些值给拼接起来,就是MD5值了
  18. * (PS:为了便于阅读,把多余的fff去掉,并且单个字符前加个0)
  19. *
  20. */
  21. public static String getMD5( String str) throws Exception {
  22. String MD5 = "";
  23. MessageDigest md5 = MessageDigest.getInstance( "MD5");
  24. byte[] bytes = str.getBytes();
  25. byte[] digest = md5.digest(bytes);
  26. for ( int i = 0; i < digest.length; i++) {
  27. //摘要字节数组中各个字节的"十六进制"形式.
  28. int j = digest[i];
  29. j = j & 0x000000ff;
  30. String s1 = Integer.toHexString(j);
  31. if (s1.length() == 1) {
  32. s1 = "0" + s1;
  33. }
  34. MD5 += s1;
  35. }
  36. return MD5;
  37. }
  38. //重载,所以用户传入"字符串"或者"文件"都可以解决.
  39. /**
  40. * 处理逻辑:
  41. * 1.现在传入的是"文件",不是字符串
  42. * 2.所以信息摘要对象.进行摘要得到数组不能像上面获得:md5.digest(bytes),因为不是str.getBytes得到bytes
  43. * 3.其实还是通过mdt.digest();获取到字节数组,但是前期必须要有一个方法必须是md5.update(),即"信息摘要对象"要先更新
  44. * 4."信息摘要更新"里面有(byte[] input),说明是读取流获取到的数组,所以我们就用这个方法.
  45. * 5.所以最终的逻辑就是:
  46. *
  47. * 1.获取文件的读取流
  48. * 2.不停的读取流中的"内容"放入字符串,放一部分就"更新"一部分.直到全部完毕
  49. * 3.然后调用md5.digest();就会得到有内容的字节数组,剩下的就和上边一样了.
  50. */
  51. public static String getMD5(File file) throws Exception {
  52. String MD5 = "";
  53. MessageDigest md5 = MessageDigest.getInstance( "MD5");
  54. FileInputStream fis = new FileInputStream(file);
  55. byte[] bytes = new byte[ 1024 * 5];
  56. int len = -1;
  57. while ((len=fis.read(bytes))!= -1) {
  58. //一部分一部分更新
  59. md5.update(bytes, 0, len);
  60. }
  61. byte[] digest = md5.digest();
  62. for ( int i = 0; i <digest.length; i++) {
  63. int n = digest[i] & 0x000000ff;
  64. String s = Integer.toHexString(n);
  65. MD5 += s;
  66. }
  67. return MD5;
  68. }
  69. }

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. 1 #ifndef MD5H
  2. 2 #define MD5H
  3. 3 #include <math.h>
  4. 4 #include <Windows.h>
  5. 5
  6. 6 void ROL(unsigned int &s, unsigned short cx); //32位数循环左移实现函数
  7. 7 void ltob(unsigned int &i); //B\L互转,接受UINT类型
  8. 8 unsigned int* MD5(const char* mStr); //接口函数,并执行数据填充,计算MD5时调用此函数
  9. 9
  10. 10 #endif
 

#MD5.cpp

 

    
    
  1. 1 #include "MD5.h"
  2. 2
  3. 3 /*4组计算函数*/
  4. 4 inline unsigned int F(unsigned int X, unsigned int Y, unsigned int Z)
  5. 5 {
  6. 6 return (X & Y) | ((~X) & Z);
  7. 7 }
  8. 8 inline unsigned int G(unsigned int X, unsigned int Y, unsigned int Z)
  9. 9 {
  10. 10 return (X & Z) | (Y & (~Z));
  11. 11 }
  12. 12 inline unsigned int H(unsigned int X, unsigned int Y, unsigned int Z)
  13. 13 {
  14. 14 return X ^ Y ^ Z;
  15. 15 }
  16. 16 inline unsigned int I(unsigned int X, unsigned int Y, unsigned int Z)
  17. 17 {
  18. 18 return Y ^ (X | (~Z));
  19. 19 }
  20. 20 /*4组计算函数结束*/
  21. 21
  22. 22 /*32位数循环左移实现函数*/
  23. 23 void ROL(unsigned int &s, unsigned short cx)
  24. 24 {
  25. 25 if (cx > 32)cx %= 32;
  26. 26 s = (s << cx) | (s >> (32 - cx));
  27. 27 return;
  28. 28 }
  29. 29
  30. 30 /*B\L互转,接收UINT类型*/
  31. 31 void ltob(unsigned int &i)
  32. 32 {
  33. 33 unsigned int tmp = i;//保存副本
  34. 34 byte *psour = (byte*)&tmp, *pdes = (byte*) &i;
  35. 35 pdes += 3;//调整指针,准备左右调转
  36. 36 for (short i = 3; i >= 0; --i)
  37. 37 {
  38. 38 CopyMemory(pdes - i, psour + i, 1);
  39. 39 }
  40. 40 return;
  41. 41 }
  42. 42
  43. 43 /*
  44. 44 MD5循环计算函数,label=第几轮循环(1 <=label<=4),lGroup数组=4个种子副本,M=数据(16组32位数指针)
  45. 45 种子数组排列方式 : --A--D--C--B--,即 lGroup[ 0]= A; lGroup[ 1]= D; lGroup[ 2]= C; lGroup[ 3]= B;
  46. 46 */
  47. 47 void AccLoop( unsigned short label, unsigned int * lGroup, void * M)
  48. 48 {
  49. 49 unsigned int * i1, * i2, * i3, * i4, TAcc, tmpi = 0; //定义 :4个指针; T表累加器; 局部变量
  50. 50 typedef unsigned int(* clac)( unsigned int X, unsigned int Y, unsigned int Z); //定义函数类型
  51. 51 const unsigned int rolarray[ 4][ 4] = {
  52. 52 { 7, 12, 17, 22 },
  53. 53 { 5, 9, 14, 20 },
  54. 54 { 4, 11, 16, 23 },
  55. 55 { 6, 10, 15, 21 }
  56. 56 };//循环左移 -位数表
  57. 57 const unsigned short mN[ 4][ 16] = {
  58. 58 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
  59. 59 { 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 },
  60. 60 { 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2 },
  61. 61 { 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 }
  62. 62 };//数据坐标表
  63. 63 const unsigned int * pM = static_cast< unsigned int*>(M);//转换类型为32位的Uint
  64. 64 TAcc = ((label - 1) * 16) + 1; //根据第几轮循环初始化T表累加器
  65. 65 clac clacArr[4] = { F, G, H, I }; //定义并初始化计算函数指针数组
  66. 66
  67. 67 /*一轮循环开始(16组->16次)*/
  68. 68 for (short i = 0; i < 16; ++i)
  69. 69 {
  70. 70 /*进行指针自变换*/
  71. 71 i1 = lGroup + (( 0 + i) % 4);
  72. 72 i2 = lGroup + (( 3 + i) % 4);
  73. 73 i3 = lGroup + (( 2 + i) % 4);
  74. 74 i4 = lGroup + (( 1 + i) % 4);
  75. 75
  76. 76 /*第一步计算开始 : A+ F( B, C, D)+ M[ i]+ T[ i+ 1] 注 :第一步中直接计算 T表*/
  77. 77 tmpi = (*i1 + clacArr[ label - 1](* i2, * i3, * i4) + pM[( mN[ label - 1][ i])] + ( unsigned int)( 0x100000000UL * abs( sin(( double)( TAcc + i)))));
  78. 78 ROL( tmpi, rolarray[ label - 1][ i % 4]);//第二步 :循环左移
  79. 79 * i1 = *i2 + tmpi;//第三步 :相加并赋值到种子
  80. 80 }
  81. 81 return;
  82. 82 }
  83. 83
  84. 84 /*接口函数,并执行数据填充*/
  85. 85 unsigned int* MD5( const char* mStr)
  86. 86 {
  87. 87 unsigned int mLen = strlen(mStr); //计算字符串长度
  88. 88 if ( mLen < 0) return 0;
  89. 89 unsigned int FillSize = 448 - (( mLen * 8) % 512); //计算需填充的 bit
  90. 90 unsigned int FSbyte = FillSize / 8; //以字节表示的填充数
  91. 91 unsigned int BuffLen = mLen + 8 + FSbyte; //缓冲区长度或者说填充后的长度
  92. 92 unsigned char * md5Buff = new unsigned char[ BuffLen]; //分配缓冲区
  93. 93 CopyMemory( md5Buff, mStr, mLen); //复制字符串到缓冲区
  94. 94
  95. 95 /*数据填充开始*/
  96. 96 md5Buff[ mLen] = 0x80; //第一个 bit填充 1
  97. 97 ZeroMemory(& md5Buff[ mLen + 1], FSbyte - 1); //其它 bit填充 0,另一可用函数为 FillMemory
  98. 98 unsigned long long lenBit = mLen * 8ULL; //计算字符串长度,准备填充
  99. 99 CopyMemory(& md5Buff[ mLen + FSbyte], & lenBit, 8); //填充长度
  100. 100 /*数据填充结束*/
  101. 101
  102. 102 /*运算开始*/
  103. 103 unsigned int LoopNumber = BuffLen / 64; //以 16个字为一分组,计算分组数量
  104. 104 unsigned int A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;//初始4个种子,小端类型
  105. 105 unsigned int * lGroup = new unsigned int[ 4]{ A, D, C, B}; //种子副本数组,并作为返回值返回
  106. 106 for ( unsigned int Bcount = 0; Bcount < LoopNumber; ++ Bcount) //分组大循环开始
  107. 107 {
  108. 108 /*进入 4次计算的小循环*/
  109. 109 for ( unsigned short Lcount = 0; Lcount < 4;)
  110. 110 {
  111. 111 AccLoop(++ Lcount, lGroup, & md5Buff[ Bcount * 64]);
  112. 112 }
  113. 113 /*数据相加作为下一轮的种子或者最终输出*/
  114. 114 A = (lGroup[0] += A);
  115. 115 B = (lGroup[3] += B);
  116. 116 C = (lGroup[2] += C);
  117. 117 D = (lGroup[1] += D);
  118. 118 }
  119. 119 /*转换内存中的布局后才能正常显示*/
  120. 120 ltob( lGroup[ 0]);
  121. 121 ltob( lGroup[ 1]);
  122. 122 ltob( lGroup[ 2]);
  123. 123 ltob( lGroup[ 3]);
  124. 124 delete[] md5Buff; //清除内存并返回
  125. 125 return lGroup;
  126. 126 }
 

 

再给出调用实例(以win32控制台应用程序为例):

#main.cpp

 

    
    
  1. 1 #include <iostream>
  2. 2 #include <string.h>
  3. 3 #include <stdlib.h>
  4. 4 #include "MD5.h"
  5. 5
  6. 6 int main(int argc, char **argv)
  7. 7 {
  8. 8 char tmpstr[256], buf[4][10];
  9. 9 std::cout << "请输入要加密的字符串:";
  10. 10 std::cin >> tmpstr;
  11. 11 unsigned int* tmpGroup = MD5(tmpstr);
  12. 12 sprintf_s(buf[0], "%8X", tmpGroup[0]);
  13. 13 sprintf_s(buf[1], "%8X", tmpGroup[3]);
  14. 14 sprintf_s(buf[2], "%8X", tmpGroup[2]);
  15. 15 sprintf_s(buf[3], "%8X", tmpGroup[1]);
  16. 16 std::cout <<"MD5:"<< buf[0] << buf[1] << buf[2] << buf[3] << std::endl;
  17. 17
  18. 18 delete[] tmpGroup;
  19. 19 return 0; //在此下断点才能看到输出的值
  20. 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了

上面的方法也可以写写成:


   
   
  1. for ( int i = 0 ; i < digest.length; i++) {
  2. //摘要字节数组中各个字节的 "十六进制"形式.
  3. String s1 = Integer.toHexString( digest[i]) ;
  4. //如果是 8个长度的,把前面的 6个f去掉,只获取后面的
  5. if ( s1.length() == 8) {
  6. s1 = s1.substring( 6) ;
  7. }
  8. if ( s1.length() == 1) {
  9. s1 = "0" + s1 ;
  10. }
  11. MD5 += s1 ;
  12. }
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值