责编 | Aholiab

封图 | CSDN 付费下载自视觉中国

出品 | CSDN(ID:CSDNnews)

php比较字符串相等这 10 行比拟字符串相等的代码给我整懵了不信你也来看看 Vue.js

抱歉用这种标题吸引你点进来了,不过你不妨看完,看看能否让你有所收成。

先直接上代码

boolean safeEqual(String a, String b) {if (a.length != b.length) {return false;}int equal = 0;for (int i = 0; i < a.length; i++) {equal |= a.charAt(i) ^ b.charAt(i);}return equal == 0;}

上面的代码是我根据原版(Scala)翻译成Java的,Scala版本(最开始吸引程序猿石头把稳力的代码)如下:

def safeEqual(a: String, b: String) = {if (a.length != b.length) {false} else {var equal = 0for (i <- Array.range(0, a.length)) {equal |= a(i) ^ b(i)}equal == 0}}

刚开始看到这段源码觉得挺奇怪的,这个函数的功能是比较两个字符串是否相等,首先“长度不等结果肯定不等,立即返回”这个很好理解。

再看看后面的,轻微动下脑筋,转弯下也能明白这个中的门道:通过异或操作1^1=0, 1^0=1, 0^0=0,来比较每一位,如果每一位都相等的话,两个字符串肯定相等,末了存储累计异或值的变量equal必定为 0,否则为 1。

再想一下呢?

for (i <- Array.range(0, a.length)) {if (a(i) ^ b(i) != 0) // or a(i) != b[i]return false}

我们常常讲性能优化,从效率角度上讲,难道不是该当只要中途创造某一位的结果不同了(即为1)就可以立即返回两个字符串不相等了吗?(如上所示)。

这个中肯定有……

再细想一下呢?

结合方法名称 safeEquals可能知道些眉目,与安全有关。

以前知道通过延迟打算等手段来提高效率的手段,但这种已经算出结果却延迟返回的,还是头一回!

我们来看看,JDK 中也有类似的方法,如下代码摘自 java.security.MessageDigest

public static boolean isEqual(byte[] digesta, byte[] digestb) {if (digesta == digestb) return true;if (digesta == || digestb == ) {return false;}if (digesta.length != digestb.length) {return false;}int result = 0;// time-constant comparisonfor (int i = 0; i < digesta.length; i++) {result |= digesta[i] ^ digestb[i];}return result == 0;}

看注释知道了,目的是为了用常量韶光繁芜度进行比较。

但这个打算过程耗费的韶光不是常量有啥风险?(脑海里响起了背景音乐:“小朋友,你是否有很多问号?”)

原形大白!

再深入探索和理解了一下,原来这么做是为了防止计时攻击(Timing Attack)。
(也有人翻译成时序攻击)

计时攻击是边信道攻击(或称\公众侧信道攻击\公众, Side Channel Attack, 简称SCA)的一种,边信道攻击是一种针对软件或硬件设计毛病,走“歪门邪道”的一种攻击办法。

这种攻击办法是通过功耗、时序、电磁泄露等办法达到破解目的。
在很多物理隔绝的环境中,每每也能出奇制胜,这类新型攻击的有效性远高于传统的密码剖析的数学方法(某百科上说的)。

这种手段可以让调用

safeEquals(\"大众abcdefghijklmn\公众, \"大众xbcdefghijklmn\公众)

(只有首位不相同)和调用

safeEquals(\公众abcdefghijklmn\"大众, \"大众abcdefghijklmn\公众)

(两个完备相同的字符串)的所耗费的韶光一样。
防止通过大量的改变输入并通过统计运行韶光来暴力破解出要比较的字符串。

举个?,如果用之前说的“高效”的办法来实现的话。
假设某个用户设置了密码为 password,通过从a到z(实际范围可能更广)不断列举第一位,终极统计创造p0000000的运行韶光比其他从任意a~z的都长(由于要到第二位才能创造不同,其他非p开头的字符串第一位不同就直接返回了),这样就能预测出用户密码的第一位很可能是p了,然后再不断一位一位迭代下去终极破解出用户的密码。

当然,以上是从理论角度剖析,确实随意马虎理解。
但实际上彷佛通过统计运行韶光总觉得不太靠谱,这个运行韶光对环境太敏感了,比如网络,内存,CPU负载等等都会影响。

但安全问题觉得更像是 “宁肯托其有,不可信其无”。
为了防止(特殊是与署名/密码验证等干系的操作)被 timing attack,目前各大措辞都供应了相应的安全比较函数。
各种软件系统(例如 OpenSSL)、框架(例如 Play)的实现也都采取了这种办法。

例如 “天下上最好的编程措辞”(粉丝较少,评论区该当打不起架来)—— php中的:

// Compares two strings using the same time whether they're equal or not.// This function should be used to mitigate timing attacks;// for instance, when testing crypt password hashes.bool hash_equals ( string $known_string , string $user_string )//This function is safe against timing attacks.boolean password_verify ( string $password , string $hash )

实在各种措辞版本的实现办法都与上面的版本差不多,将两个字符串每一位取出来异或(^)并用或(|)保存,末了通过判断结果是否为 0 来确定两个字符串是否相等。

如果刚开始没有用 safeEquals去实现,后续的版本还会通过打补丁的办法去修复这样的安全隐患。

例如 JDK 1.6.0_17 中的Release Notes中就提到了MessageDigest.isEqual中的bug的修复,如下图所示:

大家可以看看这次变更的的详细信息openjdk中的 bug fix diff[2]为:

那么,Timing Attack 真的可行吗?

我以为各大措辞的 API 都用这种实现,肯定还是有道理的,理论上该当可以被利用的。
这不,学术界的这篇论文就流传宣传用这种计时攻击的方法破解了 OpenSSL 0.9.7 的RSA加密算法了。

计时攻击每每用于攻击一些性能较弱的打算设备,例如一些智能卡。
我们通过实验创造,也能用于攻击普通的软件系统。
本文通过实验证明,通过这种计时攻击办法能够攻破一个基于 OpenSSL 的 web 做事器的私钥。
结果证明计时攻击用于进行网络攻击在实践中可行的,因此各大安全系统须要抵御这种风险。

末了,本人毕竟不是专研安全方向,以上描述是基于本人的理解,如果有不对的地方,还请大家留言指出来,感谢。