平常使用MD5加密时,自己封装处理时,常见有2个问题:
1、转换为字符串时,高位的0被舍去;
2、出现负数时会有多个连续的F;
这是因为MessageDigest返回的结果是无符号数的byte数组,所以一个byte表示2位的十六进制数时,高位可能为0,而且在JAVA中byte默认是按有符号数的来读取的,转换时会出现负数。
public class MD5Utils {
private static final char[] chs =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String getMD5(String str) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException();
}
byte[] bys = messageDigest.digest(str.getBytes());
StringBuilder sb = new StringBuilder(32);
int index;
for (byte b : bys) {
index = b & 0xff;//将负数byte按无符号数读取
sb.append(chs[index >> 4]);//16进制数的高位
sb.append(chs[index % 16]);//16进制数的低位
}
return sb.toString();//返回的结果为32位的16进制数字符串
}
}
其中,因为MessageDigest返回的结果是用无符号数来表示的,而在JAVA中默认是采用最高位来表示正负的,所以使用了位运算的方式来截取该无符号数:
index = b & 0xff;
等价于:
index = b;
if(index < 0 ){
index += 256;
}
一个byte可以表示2位的16进制数,采用Integer.toHexString(index)转换时,若小于16会只有1位16进制数。继续使用位运算+查表法来获取16进制数:
sb.append(chs[ index >> 4]);
sb.append(chs[ index % 16]);
等价于:
if(index < 16){
sb.append('0');
}
sb.append(Integer.toHexString(index));
因为没有使用Integer的toHexString(index)和toString(int i, int radix)方法,而是自己重新实现了,所以效率有所提高。