MD5摘要算法简析
1 MD5简介
1.1 概述
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
1.2 发展历史
MD2
MD4
MD5
1.3 MD5算法原理
对于MD5算法可以简要的概括为:MD5是以512位分组来处理输入信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
总体流程图如下:
1.3.1 填充
1.3.2 初始化变量
1.3.3 处理分组数据
1.3.4 输出
1.4 MD5应用
1.4.1 一致性验证
大家都知道,地球上任何人都有自己独一无二的指纹,这常常成为司法机关鉴别罪犯身份最值得信赖的方法;与之类似,MD5就可以为任何文件(不管其大小、格式、数量)产生一个同样独一无二的“数字指纹”,如果任何人对文件做了任何改动,其MD5值也就是对应的“数字指纹”都会发生变化。
1.4.2 数字签名
MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
1.4.3 安全访问认证
MD5还广泛用于操作系统的登陆认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方面。如在Unix系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在文件系统中。当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。MD5将任意长度的“字节串”映射为一个128bit的大整数,并且是通过该128bit反推原始字符串是困难的,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。所以,要遇到了md5密码的问题,比较好的办法是:你可以用这个系统中的md5()函数重新设一个密码,如admin,把生成的一串密码的Hash值覆盖原来的Hash值就行了。
1.5 MD5特点
2 java代码实现MD5摘要算法
2.1 MD5详细实现代码
1 package xin.dreaming.md5; 2 3 public class MD5 { 4 /* 5 *四个链接变量 6 */ 7 private final int A=0x67452301; 8 private final int B=0xefcdab89; 9 private final int C=0x98badcfe; 10 private final int D=0x10325476; 11 /* 12 *ABCD的临时变量 13 */ 14 private int Atemp,Btemp,Ctemp,Dtemp; 15 16 /* 17 *常量ti 18 *公式:floor(abs(sin(i+1))×(2pow32) 19 */ 20 private final int K[]={ 21 0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee, 22 0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8, 23 0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193, 24 0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51, 25 0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8, 26 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905, 27 0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681, 28 0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60, 29 0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05, 30 0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244, 31 0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92, 32 0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314, 33 0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391}; 34 /* 35 *向左位移数,计算方法未知 36 */ 37 private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7, 38 12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, 39 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10, 40 15,21,6,10,15,21,6,10,15,21,6,10,15,21}; 41 42 43 /* 44 *初始化函数 45 */ 46 private void init(){ 47 Atemp=A; 48 Btemp=B; 49 Ctemp=C; 50 Dtemp=D; 51 } 52 /* 53 *移动一定位数 54 */ 55 private int shift(int a,int s){ 56 return(a<<s)|(a>>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位 57 } 58 /* 59 *主循环 60 */ 61 private void MainLoop(int M[]){ 62 int F,g; 63 int a=Atemp; 64 int b=Btemp; 65 int c=Ctemp; 66 int d=Dtemp; 67 for(int i = 0; i < 64; i ++){ 68 if(i<16){ 69 F=(b&c)|((~b)&d); 70 g=i; 71 }else if(i<32){ 72 F=(d&b)|((~d)&c); 73 g=(5*i+1)%16; 74 }else if(i<48){ 75 F=b^c^d; 76 g=(3*i+5)%16; 77 }else{ 78 F=c^(b|(~d)); 79 g=(7*i)%16; 80 } 81 int tmp=d; 82 d=c; 83 c=b; 84 b=b+shift(a+F+K[i]+M[g],s[i]); 85 a=tmp; 86 } 87 Atemp=a+Atemp; 88 Btemp=b+Btemp; 89 Ctemp=c+Ctemp; 90 Dtemp=d+Dtemp; 91 92 } 93 /* 94 *填充函数 95 *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64) 96 *填充方式为先加一个0,其它位补零 97 *最后加上64位的原来长度 98 */ 99 private int[] add(String str){ 100 int num=((str.length()+8)/64)+1;//以512位,64个字节为一组 101 int strByte[]=new int[num*16];//64/4=16,所以有16个整数 102 for(int i=0;i<num*16;i++){//全部初始化0 103 strByte[i]=0; 104 } 105 int i; 106 for(i=0;i<str.length();i++){ 107 strByte[i>>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序 108 } 109 strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1 110 /* 111 *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位 112 */ 113 strByte[num*16-2]=str.length()*8; 114 return strByte; 115 } 116 /* 117 *调用函数 118 */ 119 public String getMD5(String source){ 120 init(); 121 int strByte[]=add(source); 122 for(int i=0;i<strByte.length/16;i++){ 123 int num[]=new int[16]; 124 for(int j=0;j<16;j++){ 125 num[j]=strByte[i*16+j]; 126 } 127 MainLoop(num); 128 } 129 return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp); 130 131 } 132 /* 133 *整数变成16进制字符串 134 */ 135 private String changeHex(int a){ 136 String str=""; 137 for(int i=0;i<4;i++){ 138 str+=String.format("%2s", Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace(' ', '0'); 139 140 } 141 return str; 142 } 143 /* 144 *单例 145 */ 146 private static MD5 instance; 147 public static MD5 getInstance(){ 148 if(instance==null){ 149 instance=new MD5(); 150 } 151 return instance; 152 } 153 154 private MD5(){}; 155 156 public static void main(String[] args){ 157 String str=MD5.getInstance().getMD5("123"); 158 System.out.println(str); 159 } 160 }
代码摘要结果:
此处代码可以参考前边MD5原理流程,了解其代码执行流程。
2.2 使用java.security.MessageDigest实现MD5摘要算法
2.2.1代码实现:
1 /** 2 * md5计算. 3 * 4 * @param datas 5 * 待计算的数据 6 * @return 计算结果 7 */ 8 public static byte[] md5(byte[] datas) { 9 MessageDigest md = null; 10 try { 11 md = MessageDigest.getInstance("MD5"); 12 md.reset(); 13 md.update(datas); 14 return md.digest(); 15 } catch (Exception e) { 16 LogUtil.writeErrorLog("MD5计算失败", e); 17 return null; 18 } 19 }
2.2.2 MessageDigest 简要说明
MessageDigest 通过其getInstance系列静态函数来进行实例化和初始化。MessageDigest 对象通过使用 update
方法处理数据。任何时候都可以调用 reset
方法重置摘要。一旦所有需要更新的数据都已经被更新了,应该调用 digest
方法之一完成哈希计算并返回结果。
对于给定数量的更新数据,digest
方法只能被调用一次。digest
方法被调用后,MessageDigest 对象被重新设置成其初始状态。
MessageDigest 的实现可随意选择是否实现 Cloneable 接口。客户端应用程可以通过尝试复制和捕获 CloneNotSupportedException 测试可复制性:
参考:
1、https://baike.baidu.com/item/MD5/212708?fr=aladdin#1_3
2、http://hubingforever.blog.163.com/blog/static/171040579201210781650340/