您的位置:首页 > 其它

消息摘要 —— MD5算法

2016-02-22 19:43 441 查看
消息摘要也叫Hash算法,它可以把不定长的输入数据转换为定长的输出数据,常见的Hash算法有
MD5(128bit)
SHA1(160bit)


Hash算法的特点:

易变性:即使源数据发生
1bit
的信息变化,输出的数据都会有不可预知的变化。

不可逆:通过摘要信息逆推出原数据是极其困难的。

主要的应用场景:

下载文件后为了保证文件的完整性,通常会校验附带的摘要信息,当网络环境恶劣时,也可以弥补CRC校验的不足。

手机登录密码验证时,通常对密码进行一次摘要计算,发送给服务端,服务端与后台数据库比对摘要信息,完成一次登录验证。这样即使数据库丢失也不会泄露用户的密码。

下面我们来用代码来实现MD5算法:

/**
* 原文:123456
* 摘要信息:E1 0A DC 39 49 BA 59 AB BE 56 E0 57 F2 0F 88 3E
*/
public static String str2MD5(String src) {
String src = "123456";
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 得到长度为32的byte数组
byte[] md5Bytes= md5.digest(src.getBytes("UTF-8"));
// 遍历byte数组,逐个转为十六进制数
StringBuilder builder = new StringBuilder();
for (byte b : md5Bytes) {
builder.append(Integer.toHexString(b));
}
}


但是这里会有一个问题,我们先来看下以上代码得到的摘要信息:

FFFFFFE1 A FFFFFFDC 39 49 FFFFFFBA 59 FFFFFFAB FFFFFFBE 56 FFFFFFE0 57 FFFFFFF2 F FFFFFF88 3E


和正确的摘要信息比对发现多了很多F,这是为什么呢?我们再看byte数组的数据:

-31 10 -36 57 73 -70 89 -85 -66 86 -32 87 -14 15 -120 62


可以发现只有负数转为十六进制会补F,当我们使用
Integer.toHexString(int i)
时,传入一个
byte
进去,这时候发生隐式转换(
byte
转为
int
),当
byte
为负数时,转换时就会自动在高位补
24bit
1,也就是多出来的六个F

为了避免这种隐式转换发生的异常,在传入
byte
时把这个数和
0xFF
进行与运算,手动将1个字节的
byte
扩展为4个字节的
int
数据:

for (byte b : md5Bytes) {
builder.append(Integer.toHexString(b & 0xFF));
}


多出的来F问题解决了,但是上面得到的摘要信息其实还有一个问题,0呢!0呢!!别急,我们来看看
Integer.toHexString(int i)
的源码:

final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
public static String toHexString(int i) {
return toUnsignedString(i, 4);
}
/**
* 第二个参数代表进制:1(二进制)、3(八进制)、4(十六进制)
*/
private static String toUnsignedString(int i, int shift) {
char[] buf = new char[32];
int charPos = 32;
int radix = 1 << shift;    // 16
int mask = radix - 1;
/**
* 假设i = 23,23 & 15 = 7(10111 & 01111 = 0111),查表得到7
* 然后右移运算:23 >>> 4 = 1(10111 >>> 4 = 1),进入下次循环查表得到1,组合起来就17
* 当i为0时就退出循环了,最后根据十六进制数的长度返回对应的字符串
*/
do {
buf[--charPos] = digits[i & mask];
i >>>= shift;
} while (i != 0);

return new String(buf, charPos, (32 - charPos));
}


可以看到,这个方法是不会在前面补零的,这就导致了我们的结果丢失了所有的零,解决这个问题其实很简单,转换前做个判断即可:

for (byte b : md5Bytes) {
if ((b & 0xFF) < 0x10) builder.append("0");    // 当这个数小于16时则在前补零
builder.append(Integer.toHexString(b & 0xFF));
}


输出结果:

E1 0A DC 39 49 BA 59 AB BE 56 E0 57 F2 0F 88 3E


这次总算对了!说了这么多,其实还有一个更简单的方法,但是就效率来说明显不如上面的:

for (byte b : md5Bytes) {
builder.append(String.format("%02x", b));
}


最后给出完整的摘要算法,可以作为一个工具类使用:

public class MD5Utils {

public static String str2MD5(String src) {
byte[] hash;

try {
hash = MessageDigest.getInstance("MD5").digest(src.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}

StringBuilder hex = new StringBuilder();
for (byte b : hash) {
if ((b & 0xFF) < 0x10) hex.append("0");
hex.append(Integer.toHexString(b & 0xFF));
}

return hex.toString().toUpperCase();
}
}


题外话:网上有在线MD5破解工具,可以通过摘要计算出原文。其实这只是一个伪破解,网站后台有个包含大量MD5摘要的数据库,这些都是已经计算好的,逆推时逐条去对比数据库中的信息。还有有个叫彩虹表的东西,支持6个字符以下的逆推,大概有100G吧,9个字符的也有,但是貌似要收费。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MD5 消息摘要