0%

为什么Java取MD5哈希值的时候要&0xff

想要得到字符串的MD5哈希值怎么办?
先来了解一下MD5哈希值的概念。

MD5散列

一般128位的MD5散列被表示为32位十六进制数字。以下是一个43位长的仅ASCII字母列的MD5散列:

1
2
MD5("The quick brown fox jumps over the lazy dog")
= 9e107d9d372bb6826bd81d3542a419d6

即使在原文中作一个小变化(比如用c取代d)其散列也会发生巨大的变化:

1
2
MD5("The quick brown fox jumps over the lazy cog")
= 1055d3e698d289f2af8663725127bd4b

也就是说,给你一串字符串,你要把它转换成固定长度的散列。
##用Java来实现MD5哈希值
Java中可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
String password = "123456";

MessageDigest md = MessageDigest.getInstance("MD5");
md.update(password.getBytes());
byte byteData[] = md.digest();

//convert the byte to hex format method 1
StringBuffer sb = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
System.out.println("Digest(in hex format):: " + sb.toString());

byte数值转换为int数值时的注意事项

那么问题来了,下面这行代码做的是啥?
Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1)
这行语句其实是将byteData中的byte类型的数据转换成十六进制的数据进行表示。
为什么这么复杂?
是因为要考虑到byteData中的负数。

考虑一下下面这段代码,试图将byte类型表示为16进制形式的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void testByteInt() {
byte a = 127;
print(a);
byte b = -127;
print(b);
byte c = 2;
print(c);
}

private static void print(byte b) {
System.out.println(Integer.toBinaryString(b));
System.out.println(Integer.toBinaryString(b & 0xff));
System.out.println(Integer.toHexString(b));
System.out.println(Integer.toHexString(b & 0xff));
System.out.println(Integer.toString(b, 16));
System.out.println(Integer.toString(b & 0xff, 16));
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1111111
1111111
7f
7f
7f
7f
11111111111111111111111110000001
10000001
ffffff81
81
-7f
81
10
10
2
2
2
2

从输出结果可以看出几个问题。

  • String的几个函数接收byte类型的参数时自动将其转换为int类型
  • int类型的负数-127用2进制数据来表示的时候,会用符号位补全,变成11111111111111111111111110000001, 而这个2进制的数据再用16进制来表示的时候却成了ffffff81,与-127的16进制表示不一致
  • 负数的十六进制表现形式例如-7f不符合哈希值的要求。哈希值中不能带有负号。所以需要将其转换一下。
  • 比较小的数值,例如2,转换成的16进制字符串也是2,不符合哈希值的长度要求。哈希值要求每个byte用2个16进制的字符来表示。

要解决上面两个问题,前面的java程序给出的答案是:

  • & 0xff来对byte数值进行补零扩展
  • + 0x100来保证每个数转换成16进制后会得到3个字符
  • .subString(1)来去掉3个字符的第一个字符

这样就能得到2个16进制的字符了。
循环一遍,就能得到相应的MD5哈希字符串了。