码农唐磊 程序猿石头 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 版本(最开始吸引程序猿石头注意力的代码)以下:cookie
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。session
for (i <- Array.range(0, a.length)) { if (a(i) ^ b(i) != 0) // or a(i) != b[i] return false }
咱们经常讲性能优化,从效率角度上讲,难道不是应该只要中途发现某一位的结果不一样了(即为1)就能够当即返回两个字符串不相等了吗?(如上所示)。
这其中确定有……ide
结合方法名称 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") (两个彻底相同的字符串)的所耗费的时间同样。防止经过大量的改变输入并经过统计运行时间来暴力破解出要比较的字符串。举个