最近看代码, 其中看到MD5加密的时候感到很有意思, 就是为什么得到了byte数组后, 每个byte转成十六进制之前, 转换成整数的时候, 都要和 0xFF 做一遍与运算.
小弟看了很是很是困惑
demo代码
public static String clacMD5(String str){
try{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());//将String转换成字节码数组,使用该数组更新md5的计算函数
byte[] digesta = md.digest();// 获取返回md5 的hash值数组
return byte2hex(digesta);
}catch(Exception e){
return null;
}
}
public static String byte2hex(byte[] b){
String hs = "";
String stmp = "";
for(int i = 0; i < b.length; i++){
stmp = (Integer.toHexString(b[n] & 0xFF));// ???
if(stmp.length() == 1){
// 该处理的目的是当得到的十六进制Hex值小于F的时候,往前补0
hs = hs + "0" + stmp;
}else{
hs = hs + stmp;
}
if(i < b.length - 1)// 获取可以删去?
hs = hs + "";
}
return hs;
}
问题点
就在于转换成HexString的时候, 为什么要和 0XFF进行与运算呢?
看了一篇博文之后,大概清楚了原因
http://www.cnblogs.com/think-in-java/p/5527389.html
原因在于Integer.toHexString() 传入的参数是个int类型的参数, 而md5得到的是一个byte数组, 当byte[i]传入之后, 底层就会被转化成int类型, byte只有8位, 而int有32位.
java底层在进行转换的时候, 如果是负数, 8位以外的24位补的都是1, 而整数, 补的都是0.
例如:
10的byte三码都是 0000 1010, 补成int, 三码就是0000 0000 0000 0000 0000 0000 0000 1010
而-10 三码就不一样了
原码:10001010 反码: 11110101 补码(反码+1): 11110110
而jvm底层将其转成int后, 补码就是: 1111 1111 1111 1111 1111 1111 1111 0110
System.out.println(Integer.toBinaryString(-10));//输出-10的二进制码
而MD5最后希望得到的是, 两位的十六进制(HexString)字符串
不妨来看看, byte 为-10和10时的HexString
byte j = -10;
System.out.println(Integer.toHexString(j)); // ffff fff6
byte i =10;
System.out.println(Integer.toHexString(i)); // 则为 a
怎么把他们变成只有两位的十六进制字符串
1111 1111 1111 1111 1111 1111 1111 0110
&0000 0000 0000 0000 0000 0000 1111 1111(0xFF)
完美地将前面java自动补充的1全部转成0 , 而最后的8位保持原来的样子;
与运算后,结果就是 1111 0110 -> 0xF6 (转换成十进制就是 246)
换言之, 因为java认为它是32位的, 所以与运算后,符号位被认为是0, 因此负数变成了一个正数!
但是如此一来, 就能保证无论是负数还是正数, 都能保证被转换成2位的HexString了.当然正数小于16的时候,只有1位,这时候就要在其前面补0了.
八位的byte表示范围是[-128,127], 与运算后,0到正数部分不变, 但是负数部分就映射到[128, 255], 映射关系为: 值 = 256 + 负数(即 -1 的HexString应当为FF, 即255)
画外音
当一些时候, 我们不需要判断传进来的数是正数还是负数, 但是希望得到的结果一定是一个正数的时候, 我们就可以通过与运算相应位的0XFF, 就能保证得到的一定是一个正数.