今天在学习Spring Security在做密码加密配置时,有个地方需要做一个二进制和十进制的转化,自己便随手写了一个,没想到,在运行了几次之后始终验证不过,郁闷的不行,由于我用的是Spring Security框架的验证策略,所以一时间也不知道错在什么地方,于是将源码导入,debug一下发现,对密码的md5加密结果比对失败,这才意识到有可能是在对MD5摘要时进行二进制转十六进制时出的问题,下面是我随手写的一个二进制转换十六进制的方法:

public static void main(String[] args) throws Exception {
		MessageDigest digest = MessageDigest.getInstance("MD5");                
		byte[] bytes = digest.digest("admin".getBytes(Charset.forName("UTF8")));
		StringBuffer sb = new StringBuffer();                                   
		for(int i = 0; i < bytes.length; i++){                                  
			// 二进制转化成十进制                                                 
			int l = 0xff & bytes[i]; 
			// 十进制转化成十六进制
			sb.append(Integer.toHexString(l));
		}                                                                       
		System.out.println(sb.toString()); 
	}

运行结果:

21232f297a57a5a743894ae4a801fc3

感觉上没什么问题,但是运行的结果却一直不对,于是在debug时,保存了框架所生成的MD5摘要结果,一眼看过去似乎也没问题,但是一对比发现,存在一个差别:

框架产生的:

21232f297a57a5a743894a0e4a801fc3

对比:

21232f297a57a5a743894ae4a801fc3  // 我的方法产生的
21232f297a57a5a743894a0e4a801fc3 // 框架输出的

发现少了一个0

为什么会少一个0呢,难道是我二进制转十进制的时候,有问题吗,还是JDK自带的API在进行十进制转二进制时存在缺陷呢?

于是将生成的二进制打印出来结果如下:

100001 100011 101111 101001 1111010 1010111 10100101 10100111 1000011 10001001 1001010 1110 1001010 10000000 11111 11000011

// 转化格式对比
10   0001     21
10   0011     23
10   1111     2f
10   1001     29
111  1010     7a
101  0111     57
1010 0101     a5
1010 0111     a7
100  0011     43
1000 1001     89
100  1010     4a
     1110     e
100  1010     4a
1000 0000     80
1    1111     1f
1100 0011     c3

发现当高四位均为0时,JDK自带的API在转十六进制时好像不予处理了

通过查看JDK的源码发现确实是这样:

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'
    };
    private static String toUnsignedString(int i, int shift) { // 转16进制时 shift=4
	char[] buf = new char[32];
	int charPos = 32;
	int radix = 1 << shift;
	int mask = radix - 1;
	do {
	    buf[--charPos] = digits[i & mask]; // 这里相当于是 i&0x0f 取的低四位
	    i >>>= shift;//取高四位,此时当i=0时,循环即结束,所以当高四位均为0时,被忽略掉了

	} while (i != 0);

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



这里不知道是不是与无符号有关,我对Java的无符号型数据类型,相对陌生。

反正按照上面的方法进行二进制转十六进制,是有问题的,所以还是用那种比较稳妥的方法,自己来取高四位和低四位分别来进行处理,代码如下(思路都是一样的,先去高四位,然后再去低四位,分别进行处理,我这里直接找来了框架的源码)粘贴如下:

public class Hex {
	private static final char[] HEX = {
	        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
	    };
        public static char[] encode(byte[] bytes) {
	        final int nBytes = bytes.length;
	        char[] result = new char[2*nBytes];
	        int j = 0;
	        for (int i=0; i < nBytes; i++) {
	        	// Char for top 4 bits
	            result[j++] = HEX[(0xF0 & bytes[i]) >>> 4 ];
	            // Bottom 4
	            result[j++] = HEX[(0x0F & bytes[i])];
	        }
	        return result;
	    }
}