>获取完整摘要算法工具类代码:
[JAVA]数字摘要算法工具类——(Hamc)MD5/SHA1/SHA256/SHA512/PBKDF2
>Java实现MD5
JAVA JDK中的security包自带了MD5的加密算法,经过加密后,会生成一个128位的二进制序列。
首先导入这个包:
import java.security.MessageDigest;
然后编写主要的业务逻辑:
try {
//1.获取算法MD5实例
MessageDigest md = MessageDigest.getInstance("MD5");
//2.MD5加密
byte[] buff = md.digest("message digest".getBytes());// "message digest" 就是密文
//3.将128位的二进制编码转为32位的16进制编码
String md5str = toHex(buff);
//4.打印
System.out.println(md5str);//输出 f96b697d7cb7938d525a2f31aaf161d0
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
其中,128bin -> 32hex的方法如下:
//将128位的二进制序列转为32位的16进制编码
public static String toHex(byte[] bytes) {
String md5str = "";
for (int i = 0; i < bytes.length; i++) {
int temp = bytes[i];
if (temp < 0) temp += 256; // 0x8* 在经过toHexString转化时,会被转为ffffff8*,需要+256保证其正值
if (temp < 16) md5str += "0"; // 0x05 转化会变成 5,缺少一位0
md5str+=Integer.toHexString(temp);
}
return md5str;
}
>MD5的算法
MD5的主要算法思路有三步:
- 补位
- 分组(小端排序)
- 循环
>补位是什么呢?
MD5将密文按512分一个大组,然后每32位分一个小组,共16小组。(也就是64字节一个大组,4个字节一个小组)。
密文总长度为len,每条密文最后要留出64位(8个字节)的长度,用于保存原数据的长度len;因此,最后一个大分组必须有448位(56字节)的补位;
举个例子,假如一个密文长度为127位(字节),由于(127%64=63)!= 48,因此,必须补到其刚好余48字节为止,也就是(64-63)+48=49个字节。
补位时,第一个字节必为0x80,其后都是0x00。(也就是第一位为1,其他位都为0)。
补位完成后,再往后面写上八个字节的长度。比如对于上面那个例子,最后8个字节要写的是7fe 00 00 00 00 00 00 00。(127 = 0x7f)
public static BYTES fill(BYTES s){
int length=s.getLength();
if(s.getLength()%64!=56){
s.add((byte)0x80);
if(length%64<56){
for(int i=0;i<56-length%64-1;i++){
s.add((byte)0x00);
}
}
else {
for(int i=0;i<56+64-length%64-1;i++){
s.add((byte)0x00);
}
}
}
String hex = Integer.toHexString(length);
int begin=-2,end=begin+2,remain=8;
while(end<hex.length()){
remain--;
begin=end;
end+=2;
if (end>hex.length())end=hex.length();
byte b = (byte)(int)Integer.valueOf(hex.substring(begin,end),16);
s.add(b);
}
for (int i=0;i<remain;i++){
s.add((byte)0x00);
}
//System.out.println(s.getLength());
return s;
}
>如何分组?
共分为16组,第j组记为M[j]。每4个字节(32位)一组,组内按小端排序。
比如M[1]对应的数据是12 13 14 15,那么M[1]=0x15141312。>
public static int[] divide(BYTES s){
int[] M = new int[16];
for(int i=0;i<16;i++){
int a = s.getI(i*4);if(a<0)a+=256;
int b = s.getI(i*4+1);if(b<0)b+=256;
int c = s.getI(i*4+2);if(c<0)c+=256;
int d = s.getI(i*4+3);if(d<0)d+=256;
String ta="";if(a<16)ta+="0";ta+=Integer.toHexString(a);
String tb="";if(b<16)tb+="0";tb+=Integer.toHexString(b);
String tc="";if(c<16)tc+="0";tc+=Integer.toHexString(c);
String td="";if(d<16)td+="0";td+=Integer.toHexString(d);
/*
System.out.println("a="+a+" "+i+" "+Integer.toHexString(a));
System.out.println("b="+b+" "+i+" "+Integer.toHexString(b));
System.out.println("c="+c+" "+i+" "+Integer.toHexString(c));
System.out.println("d="+d+" "+i+" "+Integer.toHexString(d));
*/
String temp=td+tc+tb+ta;
M[i]=Integer.valueOf(temp,16);
}
return M;
}
>循环
这是MD5的核心算法,涉及到数学,就不推导了。
总之,需要定义四个链接常量:
int A=0x67452301;
int B=0xefcdab89;
int C=0x98badcfe;
int D=0x10325476;
四个非线性函数:
public static int F(int X,int Y,int Z){
return (X&Y)|((~X)&Z);
}
public static int G(int X,int Y,int Z){
return (X&Z)|(Y&(~Z));
}
public static int H(int X,int Y,int Z){
return X^Y^Z;
}
public static int I(int X,int Y,int Z){
return Y^(X|(~Z));
}
四个循环函数:
public static int FF(int a,int b,int c,int d,int Mj,int s,int ti){
return b + ((a + F(b, c, d) + Mj + ti) << s);
}
public static int GG(int a,int b,int c,int d,int Mj,int s,int ti){
return b + ((a + G(b, c, d) + Mj + ti) << s);
}
public static int HH(int a,int b,int c,int d,int Mj,int s,int ti){
return b + ((a + H(b, c, d) + Mj + ti) << s);
}
public static int II(int a,int b,int c,int d,int Mj,int s,int ti){
return b + ((a + I(b, c, d) + Mj + ti) << s);
}
接着,进行N=sLength/64次大循环,其中sLength是补位后的数据长度(这里的单位是字节,所以除了64)。每次循环结束后,要做一个A+=a的操作。(这部分直接结合代码看吧)
整个循环的代码为:
public static String getMD5(BYTES s){
//链接常量
int A=0x67452301;
int B=0xefcdab89;
int C=0x98badcfe;
int D=0x10325476;
int a=A,b=B,c=C,d=D;
int N=s.getLength()/64;
for(int i=0;i<N;i++){
int[] M = divide(s);
//对M[j]的第一轮循环
a=FF(a,b,c,d,M[0],7,0xd76aa478);
b=FF(d,a,b,c,M[1],12,0xe8c7b756);
c=FF(c,d,a,b,M[2],17,0x242070db);
d=FF(b,c,d,a,M[3],22,0xc1bdceee);
a=FF(a,b,c,d,M[4],7,0xf57c0faf);
b=FF(d,a,b,c,M[5],12,0x4787c62a);
c=FF(c,d,a,b,M[6],17,0xa8304613);
d=FF(b,c,d,a,M[7],22,0xfd469501) ;
a=FF(a,b,c,d,M[8],7,0x698098d8) ;
b=FF(d,a,b,c,M[9],12,0x8b44f7af) ;
c=FF(c,d,a,b,M[10],17,0xffff5bb1) ;
d=FF(b,c,d,a,M[11],22,0x895cd7be) ;
a=FF(a,b,c,d,M[12],7,0x6b901122) ;
b=FF(d,a,b,c,M[13],12,0xfd987193) ;
c=FF(c,d,a,b,M[14],17,0xa679438e) ;
d=FF(b,c,d,a,M[15],22,0x49b40821);
//对M[j]的第二轮循环
a=GG(a,b,c,d,M[1],5,0xf61e2562);
b=GG(d,a,b,c,M[6],9,0xc040b340);
c=GG(c,d,a,b,M[11],14,0x265e5a51);
d=GG(b,c,d,a,M[0],20,0xe9b6c7aa) ;
a=GG(a,b,c,d,M[5],5,0xd62f105d) ;
b=GG(d,a,b,c,M[10],9,0x02441453) ;
c=GG(c,d,a,b,M[15],14,0xd8a1e681);
d=GG(b,c,d,a,M[4],20,0xe7d3fbc8) ;
a=GG(a,b,c,d,M[9],5,0x21e1cde6) ;
b=GG(d,a,b,c,M[14],9,0xc33707d6) ;
c=GG(c,d,a,b,M[3],14,0xf4d50d87) ;
d=GG(b,c,d,a,M[8],20,0x455a14ed);
a=GG(a,b,c,d,M[13],5,0xa9e3e905);
b=GG(d,a,b,c,M[2],9,0xfcefa3f8) ;
c=GG(c,d,a,b,M[7],14,0x676f02d9) ;
d=GG(b,c,d,a,M[12],20,0x8d2a4c8a);
//对M[j]的第三轮循环
a=HH(a,b,c,d,M[5],4,0xfffa3942);
b=HH(d,a,b,c,M[8],11,0x8771f681);
c=HH(c,d,a,b,M[11],16,0x6d9d6122);
d=HH(b,c,d,a,M[14],23,0xfde5380c) ;
a=HH(a,b,c,d,M[1],4,0xa4beea44) ;
b=HH(d,a,b,c,M[4],11,0x4bdecfa9) ;
c=HH(c,d,a,b,M[7],16,0xf6bb4b60) ;
d=HH(b,c,d,a,M[10],23,0xbebfbc70);
a=HH(a,b,c,d,M[13],4,0x289b7ec6);
b=HH(d,a,b,c,M[0],11,0xeaa127fa);
c=HH(c,d,a,b,M[3],16,0xd4ef3085);
d=HH(b,c,d,a,M[6],23,0x04881d05);
a=HH(a,b,c,d,M[9],4,0xd9d4d039);
b=HH(d,a,b,c,M[12],11,0xe6db99e5);
c=HH(c,d,a,b,M[15],16,0x1fa27cf8) ;
d=HH(b,c,d,a,M[2],23,0xc4ac5665);
//对M[j]的第四轮循环
a=II(a,b,c,d,M[0],6,0xf4292244) ;
b=II(d,a,b,c,M[7],10,0x432aff97) ;
c=II(c,d,a,b,M[14],15,0xab9423a7);
d=II(b,c,d,a,M[5],21,0xfc93a039) ;
a=II(a,b,c,d,M[12],6,0x655b59c3) ;
b=II(d,a,b,c,M[3],10,0x8f0ccc92) ;
c=II(c,d,a,b,M[10],15,0xffeff47d);
d=II(b,c,d,a,M[1],21,0x85845dd1) ;
a=II(a,b,c,d,M[8],6,0x6fa87e4f) ;
b=II(d,a,b,c,M[15],10,0xfe2ce6e0);
c=II(c,d,a,b,M[6],15,0xa3014314) ;
d=II(b,c,d,a,M[13],21,0x4e0811a1);
a=II(a,b,c,d,M[4],6,0xf7537e82) ;
b=II(d,a,b,c,M[11],10,0xbd3af235);
c=II(c,d,a,b,M[2],15,0x2ad7d2bb);
d=II(b,c,d,a,M[9],21,0xeb86d391);
A += a;
B += b;
C += c;
D += d;
}
if(A<0)A+=256;
if(B<0)B+=256;
if(C<0)C+=256;
if(D<0)D+=256;
return Integer.toHexString(A)+" "+Integer.toHexString(B)+" "+Integer.toHexString(C)+" "+Integer.toHexString(D);
}
>MD5的盐运算
MD5理论上不可能求逆,但仍经不住黑客通过暴力解法去求解。例如黑客从数据库里得到一个序列为M的经过MD5加密的密码序列,那么他可以在他本机上经过大量摘要运算,当某个字符串S的MD5计算结果等于M时,就可以知道S就是原密码了。
盐运算可以较好得预防这个情况:
- 每次随机一个16位的Hex序列,如0x254f58d21a,把这个序列R作为字符串,直接加到密码序列S之后,也就是S+="254f58d21a";
- 对这个序列做MD5,并对结果M,把后16位或你指定的某16位用序列R替换;(当然更好的方法是按某种只有你知道的方式,插入到MD5序列中,可充分保证唯一性);
- 验证用户密码S时,从数据库存储的MD5码提取R(你已知其如何插入或替换也就知道如何提取),然后重复1、2两步,最后与数据库中的MD5校对即可。