抱歉用这种标题吸引你点进来了,不过你不妨看完,看看可否让你有所收获。(有收获,请评论区留个言,没收获,下周末我直播吃**,哈哈,这你也信)php
补充说明:微信公众号改版,对各个号主影响还挺大的。目前从后台数据来看,对我影响不大,由于我这反正都是小号,😂阅读量自己就少的可怜,真相了,🐶狗头(刚从交流群学会的表情)。html
先直接上代码:java
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
版本(最开始吸引程序猿石头注意力的代码)以下:web
def safeEqual(a: String, b: String) = {
if (a.length != b.length) {
false
} else {
var equal = 0
for (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
可能知道些眉目,与安全有关。服务器
本文开篇的代码来自playframewok 里用来验证cookie(session)中的数据是否合法(包含签名的验证),也是石头写这篇文章的由来。微信
之前知道经过延迟计算等手段来提升效率的手段,但这种已经算出结果却延迟返回的,仍是头一回!
咱们来看看,JDK 中也有相似的方法,以下代码摘自 java.security.MessageDigest
:
public static boolean isEqual(byte[] digesta, byte[] digestb) {
if (digesta == digestb) return true;
if (digesta == null || digestb == null) {
return false;
}
if (digesta.length != digestb.length) {
return false;
}
int result = 0;
// time-constant comparison
for (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[1]中就提到了MessageDigest.isEqual
中的bug的修复,以下图所示:
MessageDigest timing attack vulnerabilities
你们能够看看此次变动的的详细信息openjdk中的 bug fix diff[2]为:
MessageDigest.isEqual计时攻击
我以为各大语言的 API 都用这种实现,确定仍是有道理的,理论上应该能够被利用的。 这不,学术界的这篇论文就宣称用这种计时攻击的方法破解了 OpenSSL 0.9.7
的RSA加密算法了。关于 RSA 算法的介绍能够看看以前本人写的这篇文章。
这篇Remote Timing Attacks are Practical[3] 论文中指出(我大体翻译下摘要,感兴趣的同窗能够经过文末连接去看原文):
计时攻击每每用于攻击一些性能较弱的计算设备,例如一些智能卡。咱们经过实验发现,也能用于攻击普通的软件系统。本文经过实验证实,经过这种计时攻击方式可以攻破一个基于 OpenSSL 的 web 服务器的私钥。结果证实计时攻击用于进行网络攻击在实践中可行的,所以各大安全系统须要抵御这种风险。
最后,本人毕竟不是专研彻底方向,以上描述是基于本人的理解,若是有不对的地方,还请你们留言指出来。感谢。
补充说明2:感谢正在阅读文章的你,让我还有动力继续坚持更新原创。
本人发文很少,但但愿写的文章能达到的目的是:占用你的阅读时间,就尽可能可以让你有所收获。
若是你以为个人文章有所帮助,还请你帮忙转发分享,另外请别忘了点击公众号右上角加个星标,好让你别错事后续的精彩文章(微信改版了,或许我发的文章都不能推送到你那了)。
原创真心不易,但愿你能帮我个小忙呗,若是本文内容你以为有所启发,有所收获,请帮忙点个“在看”呗,或者转发分享让更多的小伙伴看到。 参考资料: