面试编程基本功的时候,很常见的一个题目是:
判断两个字符串是否完全相同?
很多同学能够很快的写出对应的代码:
public static boolean isEqual(byte[] a, byte[] b) {
// 先判断长度是否相同
if (a.length != b.length) {
// 长度不同,返回false
return false;
}
// 一个一个字符,循环遍历判断
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
// 只要一个字符不同,返回false
return false;
}
}
// 全部字符相同,返回true
return true;
}
代码没有问题,甚至JDK底层,也是这么实现的。
然而,MessageDigest.isEqual却被报了bug,并在JDK 1.6.0_17中被fix成了以下的版本:
public static boolean isEqual(byte[] a, byte[] b) {
// 先判断长度是否相同
if (a.length != b.length) {
// 长度不同,返回false
return false;
}
// 返回结果初始化
int result = 0;
// 一个一个字符,循环遍历判断
for (int i = 0; i < a.length; i++) {
// 字符串比较,采用了“按位异或”
// 每一个比较结果,都“按位或”给了返回结果
result |= a[i] ^ b[i];
}
// 返回结果为0,说明字符串全部相同,返回true
return (result == 0);
}
首先,字符比较,升级成了“按位异或”。
这个不难理解,对于两个字符x和y:
(1)如果x == y,则有 x^y == 0
(2)如果x != y,则有 x^y != 0
其次,就代码的正确性来说,新代码并没有问题:
(1)当所有字符都相同时,result必为0,两个字符串才完全相同,返回true;
(2)只要有两个字符不同,result必不为0,一定会返回false;
同时,当输入的参数,是两个相同的字符串时,新旧算法的时间复杂度是相同的:都需要遍历每一个字符,然后返回true。
可是,当输入的参数,是两个不同的字符串时:
(1)旧版本代码,只要发现两个字符串有1个字符不同,直接返回false;
(2)新版本代码,会坚持检查完所有字符,再返回false;
这里大家就要有疑问了,新版本的代码,性能不是降低了吗?
要更彻底的解释这个问题,先得从计时攻击(Timing Attack)说起。
传统的hacker,如何破解密码?
最常见的,采用暴力穷举破解。但当密码位数较长,字符值域较广的时候,破解难度较大。
新型计时攻击(Timing Attack),是怎么破解密码?
第一步,要猜测密码的长度。
hacker不停的测试不同长度的“探测密码”,然后对执行时间进行计时。
hacker可以使用:
a
aa
aaa
aaaa
...
作为探测密码。