目录
简介
MD5 算法底层原理:
第一步:处理原文
第二步:设置初始值
第三步:循环处理分组数据
参数及函数
第四步:拼接结果
代码实现:
java 实现MD5加密原理
使用Java自带的MessageDigest工具类实现
MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由MIT Laboratory for Computer Science和RSA Data Security Inc的Ronald L. Rivest开发出来,经MD2、MD3和MD4发展而来。
简单概括起来,MD5 算法的过程分为四步:处理原文,设置初始值,循环处理分组数据,拼接结果。
总体流程如下图所示,表示第i个分组,每次的运算都由前一轮的128位结果值和第i块512bit值进行运算。
首先,我们计算出原文长度(bit)对 512 求余的结果,如果不等于 448,就需要填充原文使得原文对 512 求余的结果等于 448。填充的方法是第一位填充 1,其余位填充 0。填充完后,信息的长度就是 512*N+448。
之后,用剩余的位置(512-448=64 位)记录原文的真正长度,把长度的二进制值补在最后。这样处理后的信息长度就是 512*(N+1)。
MD5 的哈希结果长度为 128 位,按每 32 位分成一组共 4 组。这 4 组结果是由 4 个初始值 A、B、C、D 经过不断演变得到。MD5 的官方实现中,A、B、C、D 的初始值如下(16 进制):
A=0x01234567
B=0x89ABCDEF
C=0xFEDCBA98
D=0x76543210
它们称为链接变量(chaining variable)
一共进行多少次循环呢?由处理后的原文长度决定。
假设处理后的原文长度是 M(bit)
主循环次数 = M / 512
每个主循环中包含 512 / 32 * 4 = 64 次 子循环
每一分组的算法流程如下:
第一分组需要将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。从第二分组开始的变量为上一分组的运算结果,即a = A, b = B, c = C, d = D。
主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。
1.F非线性函数.官方 MD5 所用到的函数有四种:
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))
(&是与,|是或,~是非,^是异或)
这些函数是这样设计的:如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。
函数F是按逐位方式操作:如果X,那么Y,否则Z。函数H是逐位奇偶操作符。
在主循环下面 64 次子循环中,F、G、H、I 交替使用,第一轮 16 次使用 F,第二轮 16 次使用 G,第三轮 16 次使用 H,第四轮 16 次使用 I。
2.Mj
Mj是第一步处理后的原文。在第一步中,处理后原文的长度是 512 的整数倍。把原文的每 512 位再分成 16 等份,命名为 M0~M15,每一等份长度 32。在 64 次子循环中,每 16 次循环,都会交替用到 M1~M16 之一。
3.ti
一个常量,在 64 次子循环中,每一次用到的常量都是不同的。
常数ti可以如下t选择:
在第i步中,ti是4294967296(2的32次方)*abs(sin(i))的整数部分,i的单位是弧度。
4.<<
左移 S 位,S 的值也是常量。
命名规则(s+方法名+第N个数)
SFF1=7; SFF2=12, SFF3=17; SFF4=22;
SGG1=5; SGG2=9, SGG3=14; SGG4=20;
SHH1=4; SHH2=11, SHH3=16; SHH4=23;
SⅡ1=6; SⅡ2=10, SⅡ3=15; SⅡ4=21;
设Mj表示消息的第j个子分组(从0到15),<<
FF(a,b,c,d,Mj,s,ti)表示a=b+((a+(F(b,c,d)+Mj+ti)<<
GG(a,b,c,d,Mj,s,ti)表示a=b+((a+(G(b,c,d)+Mj+ti)<<
HH(a,b,c,d,Mj,s,ti)表示a=b+((a+(H(b,c,d)+Mj+ti)<<
II(a,b,c,d,Mj,s,ti)表示a=b+((a+(I(b,c,d)+Mj+ti)<<
总结一下主循环中的 64 次子循环,可以归纳为下面的四轮:
这四轮(64步)是:
第一轮:
FF(a,b,c,d,M0,7,0xd76aa478) s[0]=7, K[0] = 0xd76aa478
FF(a,b,c,d,M1,12,0xe8c7b756) s[1]=12, K[1] = 0xe8c7b756
FF(a,b,c,d,M2,17,0x242070db)
FF(a,b,c,d,M3,22,0xc1bdceee)
FF(a,b,c,d,M4,7,0xf57c0faf)
FF(a,b,c,d,M5,12,0x4787c62a)
FF(a,b,c,d,M6,17,0xa8304613)
FF(a,b,c,d,M7,22,0xfd469501)
FF(a,b,c,d,M8,7,0x698098d8)
FF(a,b,c,d,M9,12,0x8b44f7af)
FF(a,b,c,d,M10,17,0xffff5bb1)
FF(a,b,c,d,M11,22,0x895cd7be)
FF(a,b,c,d,M12,7,0x6b901122)
FF(a,b,c,d,M13,12,0xfd987193)
FF(a,b,c,d,M14,17, 0xa679438e)
FF(a,b,c,d,M15,22,0x49b40821)
第二轮:
GG(a,b,c,d,M1,5,0xf61e2562)
GG(a,b,c,d,M6,9,0xc040b340)
GG(a,b,c,d,M11,14,0x265e5a51)
GG(a,b,c,d,M0,20,0xe9b6c7aa)
GG(a,b,c,d,M5,5,0xd62f105d)
GG(a,b,c,d,M10,9,0×02441453)
GG(a,b,c,d,M15,14,0xd8a1e681)
GG(a,b,c,d,M4,20,0xe7d3fbc8)
GG(a,b,c,d,M9,5,0x21e1cde6)
GG(a,b,c,d,M14,9,0xc33707d6)
GG(a,b,c,d,M3,14,0xf4d50d87)
GG(a,b,c,d,M8,20,0x455a14ed)
GG(a,b,c,d,M13,5,0xa9e3e905)
GG(a,b,c,d,M2,9,0xfcefa3f8)
GG(a,b,c,d,M7,14,0x676f02d9)
GG(a,b,c,d,M12,20,0x8d2a4c8a)
第三轮:
HH(a,b,c,d,M5,4,0xfffa3942)
HH(a,b,c,d,M8,11,0x8771f681)
HH(a,b,c,d,M11,16,0x6d9d6122)
HH(a,b,c,d,M14,23,0xfde5380c)
HH(a,b,c,d,M1,4,0xa4beea44)
HH(a,b,c,d,M4,11,0x4bdecfa9)
HH(a,b,c,d,M7,16,0xf6bb4b60)
HH(a,b,c,d,M10,23,0xbebfbc70)
HH(a,b,c,d,M13,4,0x289b7ec6)
HH(a,b,c,d,M0,11,0xeaa127fa)
HH(a,b,c,d,M3,16,0xd4ef3085)
HH(a,b,c,d,M6,23,0x04881d05)
HH(a,b,c,d,M9,4,0xd9d4d039)
HH(a,b,c,d,M12,11,0xe6db99e5)
HH(a,b,c,d,M15,16,0x1fa27cf8)
HH(a,b,c,d,M2,23,0xc4ac5665)
第四轮:
Ⅱ(a,b,c,d,M0,6,0xf4292244)
Ⅱ(a,b,c,d,M7,10,0x432aff97)
Ⅱ(a,b,c,d,M14,15,0xab9423a7)
Ⅱ(a,b,c,d,M5,21,0xfc93a039)
Ⅱ(a,b,c,d,M12,6,0x655b59c3)
Ⅱ(a,b,c,d,M3,10,0x8f0ccc92)
Ⅱ(a,b,c,d,M10,15,0xffeff47d)
Ⅱ(a,b,c,d,M1,21,0x85845dd1)
Ⅱ(a,b,c,d,M8,6,0x6fa87e4f)
Ⅱ(a,b,c,d,M15,10,0xfe2ce6e0)
Ⅱ(a,b,c,d,M6,15,0xa3014314)
Ⅱ(a,b,c,d,M13,21,0x4e0811a1)
Ⅱ(a,b,c,d,M4,6,0xf7537e82)
Ⅱ(a,b,c,d,M11,10,0xbd3af235)
Ⅱ(a,b,c,d,M2,15,0x2ad7d2bb)
Ⅱ(a,b,c,d,M9,21,0xeb86d391)
所有这些完成之后,将A,B,C,D分别加上a,b,c,d。然后用下一分组数据继续运行算法,最后的输出是A,B,C和D的级联。
import java.util.Arrays;
public class MD5 {
//static final String hexs[]={"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
//标准的幻数
private static final int A=0x67452301;
private static final int B=0xefcdab89;
private static final int C=0x98badcfe;
private static final int D=0x10325476;
//下面这些S11-S44实际上是一个4*4的矩阵,在四轮循环运算中用到
static final int S11 = 7;
static final int S12 = 12;
static final int S13 = 17;
static final int S14 = 22;
static final int S21 = 5;
static final int S22 = 9;
static final int S23 = 14;
static final int S24 = 20;
static final int S31 = 4;
static final int S32 = 11;
static final int S33 = 16;
static final int S34 = 23;
static final int S41 = 6;
static final int S42 = 10;
static final int S43 = 15;
static final int S44 = 21;
//java不支持无符号的基本数据(unsigned)
private int [] result={A,B,C,D};//存储hash结果,共4×32=128位,初始化值为(幻数的级联)
//此重载方法是将传入的字符串加点特色(比如密码加密可以连同将用户名加入一起加密,这样就算密码和别人一样加密后的结果也不一样)
private byte[] digest(String inputStr ,String salt) {
return digest(salt+inputStr);
}
private byte[] digest(String inputStr){
byte [] inputBytes=inputStr.getBytes();//将字符串转化成字节数组
int byteLen=inputBytes.length;//长度(字节)
int groupCount=0;//完整分组的个数
groupCount=byteLen/64;//每组512位(64字节)
int []groups=null;//每个小组(64字节)再细分后的16个小组(4字节)
//处理每一个完整分组
for(int step=0;step>8;//取完移除低八位
}
groups=divGroup(tempBytes,0);
trans(groups);//处理分组
}else{//余数大于56,需增加一组主循环,目的保证处理后的原文最后64位(8字节)储存长度
for(int i=0;i>8;
}
groups=divGroup(tempBytes,0);
trans(groups);//处理分组
}
//System.out.println(Arrays.toString(result));
//将hash值转换成字节数组
//int[] result={A,B,C,D};共4×32=128位,每八位转换一个byte类型,储存在一个结果集
byte[] resultByte = new byte[16];
for(int i = 0;i<4;i++) {
for(int j = 0; j < 4; j++) {
//每次取低八位
resultByte[i*4+j] = (byte) (result[i] & 0xff);
//取完移除
result[i]=result[i]>>8;
}
}
//System.out.println(Arrays.toString(resultByte));
return resultByte;
}
/**
* 从inputBytes的index开始取512位,作为新的分组
* 将每一个512位的分组再细分成16个小组,每个小组64位(8个字节)
* @param inputBytes
* @param index
* @return
*/
private static int[] divGroup(byte[] inputBytes,int index){
int [] temp=new int[16];
for(int i=0;i<16;i++){
//从byte数组中取四个元素组成一个int类型保存在数组中
//b2iu方法是将byte的最高位符号位转化为代表数值位
temp[i]=b2iu(inputBytes[4*i+index])|
(b2iu(inputBytes[4*i+1+index]))<<8|
(b2iu(inputBytes[4*i+2+index]))<<16|
(b2iu(inputBytes[4*i+3+index]))<<24;
}
//最后返回这分组
return temp;
}
/**
* 这时不存在符号位(符号位存储不再是代表正负),所以需要处理一下
* @param b
* @return
*/
public static int b2iu(byte b){//0x7F + 128=0xff
return b < 0 ? b & 0x7F + 128 : b;
}
/**
* 主要的操作,四轮循环
* @param groups[]--每一个分组512位(64字节)
*/
private void trans(int[] groups) {
int a = result[0], b = result[1], c = result[2], d = result[3];
/*第一轮*/
a = FF(a, b, c, d, groups[0], S11, 0xd76aa478); /* 1 */
d = FF(d, a, b, c, groups[1], S12, 0xe8c7b756); /* 2 */
c = FF(c, d, a, b, groups[2], S13, 0x242070db); /* 3 */
b = FF(b, c, d, a, groups[3], S14, 0xc1bdceee); /* 4 */
a = FF(a, b, c, d, groups[4], S11, 0xf57c0faf); /* 5 */
d = FF(d, a, b, c, groups[5], S12, 0x4787c62a); /* 6 */
c = FF(c, d, a, b, groups[6], S13, 0xa8304613); /* 7 */
b = FF(b, c, d, a, groups[7], S14, 0xfd469501); /* 8 */
a = FF(a, b, c, d, groups[8], S11, 0x698098d8); /* 9 */
d = FF(d, a, b, c, groups[9], S12, 0x8b44f7af); /* 10 */
c = FF(c, d, a, b, groups[10], S13, 0xffff5bb1); /* 11 */
b = FF(b, c, d, a, groups[11], S14, 0x895cd7be); /* 12 */
a = FF(a, b, c, d, groups[12], S11, 0x6b901122); /* 13 */
d = FF(d, a, b, c, groups[13], S12, 0xfd987193); /* 14 */
c = FF(c, d, a, b, groups[14], S13, 0xa679438e); /* 15 */
b = FF(b, c, d, a, groups[15], S14, 0x49b40821); /* 16 */
/*第二轮*/
a = GG(a, b, c, d, groups[1], S21, 0xf61e2562); /* 17 */
d = GG(d, a, b, c, groups[6], S22, 0xc040b340); /* 18 */
c = GG(c, d, a, b, groups[11], S23, 0x265e5a51); /* 19 */
b = GG(b, c, d, a, groups[0], S24, 0xe9b6c7aa); /* 20 */
a = GG(a, b, c, d, groups[5], S21, 0xd62f105d); /* 21 */
d = GG(d, a, b, c, groups[10], S22, 0x2441453); /* 22 */
c = GG(c, d, a, b, groups[15], S23, 0xd8a1e681); /* 23 */
b = GG(b, c, d, a, groups[4], S24, 0xe7d3fbc8); /* 24 */
a = GG(a, b, c, d, groups[9], S21, 0x21e1cde6); /* 25 */
d = GG(d, a, b, c, groups[14], S22, 0xc33707d6); /* 26 */
c = GG(c, d, a, b, groups[3], S23, 0xf4d50d87); /* 27 */
b = GG(b, c, d, a, groups[8], S24, 0x455a14ed); /* 28 */
a = GG(a, b, c, d, groups[13], S21, 0xa9e3e905); /* 29 */
d = GG(d, a, b, c, groups[2], S22, 0xfcefa3f8); /* 30 */
c = GG(c, d, a, b, groups[7], S23, 0x676f02d9); /* 31 */
b = GG(b, c, d, a, groups[12], S24, 0x8d2a4c8a); /* 32 */
/*第三轮*/
a = HH(a, b, c, d, groups[5], S31, 0xfffa3942); /* 33 */
d = HH(d, a, b, c, groups[8], S32, 0x8771f681); /* 34 */
c = HH(c, d, a, b, groups[11], S33, 0x6d9d6122); /* 35 */
b = HH(b, c, d, a, groups[14], S34, 0xfde5380c); /* 36 */
a = HH(a, b, c, d, groups[1], S31, 0xa4beea44); /* 37 */
d = HH(d, a, b, c, groups[4], S32, 0x4bdecfa9); /* 38 */
c = HH(c, d, a, b, groups[7], S33, 0xf6bb4b60); /* 39 */
b = HH(b, c, d, a, groups[10], S34, 0xbebfbc70); /* 40 */
a = HH(a, b, c, d, groups[13], S31, 0x289b7ec6); /* 41 */
d = HH(d, a, b, c, groups[0], S32, 0xeaa127fa); /* 42 */
c = HH(c, d, a, b, groups[3], S33, 0xd4ef3085); /* 43 */
b = HH(b, c, d, a, groups[6], S34, 0x4881d05); /* 44 */
a = HH(a, b, c, d, groups[9], S31, 0xd9d4d039); /* 45 */
d = HH(d, a, b, c, groups[12], S32, 0xe6db99e5); /* 46 */
c = HH(c, d, a, b, groups[15], S33, 0x1fa27cf8); /* 47 */
b = HH(b, c, d, a, groups[2], S34, 0xc4ac5665); /* 48 */
/*第四轮*/
a = II(a, b, c, d, groups[0], S41, 0xf4292244); /* 49 */
d = II(d, a, b, c, groups[7], S42, 0x432aff97); /* 50 */
c = II(c, d, a, b, groups[14], S43, 0xab9423a7); /* 51 */
b = II(b, c, d, a, groups[5], S44, 0xfc93a039); /* 52 */
a = II(a, b, c, d, groups[12], S41, 0x655b59c3); /* 53 */
d = II(d, a, b, c, groups[3], S42, 0x8f0ccc92); /* 54 */
c = II(c, d, a, b, groups[10], S43, 0xffeff47d); /* 55 */
b = II(b, c, d, a, groups[1], S44, 0x85845dd1); /* 56 */
a = II(a, b, c, d, groups[8], S41, 0x6fa87e4f); /* 57 */
d = II(d, a, b, c, groups[15], S42, 0xfe2ce6e0); /* 58 */
c = II(c, d, a, b, groups[6], S43, 0xa3014314); /* 59 */
b = II(b, c, d, a, groups[13], S44, 0x4e0811a1); /* 60 */
a = II(a, b, c, d, groups[4], S41, 0xf7537e82); /* 61 */
d = II(d, a, b, c, groups[11], S42, 0xbd3af235); /* 62 */
c = II(c, d, a, b, groups[2], S43, 0x2ad7d2bb); /* 63 */
b = II(b, c, d, a, groups[9], S44, 0xeb86d391); /* 64 */
/*加入到之前计算的结果当中*/
result[0] += a;
result[1] += b;
result[2] += c;
result[3] += d;
result[0]=result[0]&0xFFFFFFFF;
result[1]=result[1]&0xFFFFFFFF;
result[2]=result[2]&0xFFFFFFFF;
result[3]=result[3]&0xFFFFFFFF;
}
/**
* 下面是处理要用到的线性函数
*/
private static int F(int x, int y, int z) {
return (x & y) | ((~x) & z);
}
private static int G(int x, int y, int z) {
return (x & z) | (y & (~z));
}
private static int H(int x, int y, int z) {
return x ^ y ^ z;
}
private static int I(int x, int y, int z) {
return y ^ (x | (~z));
}
private static int FF(int a, int b, int c, int d, int x, int s,
int ac) {
a += (F(b, c, d)&0xFFFFFFFF) + x + ac;
//<<>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}
private static int GG(int a, int b, int c, int d, int x, int s,
int ac) {
a += (G(b, c, d)&0xFFFFFFFF) + x + ac;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}
private static int HH(int a, int b, int c, int d, int x, int s,
long ac) {
a += (H(b, c, d)&0xFFFFFFFF) + x + ac;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}
private static int II(int a, int b, int c, int d, int x, int s,
long ac) {
a += (I(b, c, d)&0xFFFFFFFF) + x + ac;
a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
a += b;
return (a&0xFFFFFFFF);
}
//清除缓存,将int[] result={A,B,C,D},还原为初始状态
public void reset() {
result[0] = A;
result[1] = B;
result[2] = C;
result[3] = D;
}
public static void main(String []args){
MD5 md=new MD5();
//byte[] bytes = md.digest("123").getBytes();
System.out.println("----");
md.reset();
byte[] digest1 = md.digest("123");
System.out.println(Arrays.toString(digest1));
md.reset();
byte[] digest2 = md.digest("123","francis");
System.out.println(Arrays.toString(digest2));
}
}
//结果:
//[32, 44, -71, 98, -84, 89, 7, 91, -106, 75, 7, 21, 45, 35, 75, 112]
//[-115, 112, -101, 75, 100, 97, -82, -10, 20, 82, -102, -125, -40, -125, -58, 75]
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Md5Test {
public static void main(String[] args) throws IOException {
String s = "123";
String md5 = Md5(s);
Md5("francis123");
// File file = new File("D:/0439 - 副本.jpg");
// Md5(file);
// System.out.println("完成");
}
/**
*
* @param str 要加密的字符串
* @return
*/
public static String Md5(String str) {
//获取md5对象
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//将字符串转换为字节数组
byte[] bytes = str.getBytes();
//清除默认缓存
md5.reset();
//信息摘要对象对字节数组进行摘要,得到摘要字节数组
byte[] digest = md5.digest(bytes);
//打印摘要数组信息
System.out.println("摘要数组长度:"+digest.length);
System.out.println("摘要数组内容:"+Arrays.toString(digest));
StringBuffer hexValue = new StringBuffer();
//把摘要数组的每一个字节转换成16进制
for (int i = 0; i < digest.length; i++) {
//将字节数组转换为16进制字符串
String hexString = Integer.toHexString(digest[i] & 0xff);
//如果字符长度小于2,先追加一个0
if(hexString.length() == 1) {
hexValue.append("0");
}
//追加
hexValue.append(hexString);
}
return hexValue.toString();
}
public static void Md5(File file) throws IOException {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] buff = new byte[16];
int len;
while(( len = raf.read(buff)) != -1) {
md5.reset();
md5.update(buff, 0, len);
byte[] digest = md5.digest();
raf.seek(raf.getFilePointer()-len);
raf.write(digest);
//raf.seek(raf.getFilePointer()+len-digest.length);
//raf.skipBytes((int) (raf.getFilePointer()+len-digest.length));
}
raf.close();
}
//可逆的加密算法,加盐加密
public static String KL(String str) {
char[] charArray = str.toCharArray();
for (int i = 0; i < charArray.length; i++) {
charArray[i] = (char) (charArray[i] ^ 't');
}
String s = new String(charArray);
return s;
}
}
//结果:
//摘要数组长度:16
//摘要数组内容:[32, 44, -71, 98, -84, 89, 7, 91, -106, 75, 7, 21, 45, 35, 75, 112]
//摘要数组长度:16
//摘要数组内容:[-115, 112, -101, 75, 100, 97, -82, -10, 20, 82, -102, -125, -40, -125, -58, 75]
对比两个代码结果是一致的,到此基本实现MD5算法原理.