这 10 行比较字符串相等的代码给我整懵了,不信你也来看看!
先直接上代碼:
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?可能知道些眉目,與安全有關。
本文開篇的代碼來自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?comparisonfor?(int?i?=?0;?i?<?digesta.length;?i++)?{result?|=?digesta[i]?^?digestb[i];}return?result?==?0; }看注釋知道了,目的是為了用常量時間復雜度進行比較。
但這個計算過程耗費的時間不是常量有啥風險?(腦海里響起了背景音樂:“小朋友,你是否有很多問號?”)
真相大白
再深入探索和了解了一下,原來這么做是為了防止計時攻擊(Timing Attack)。(也有人翻譯成時序攻擊)
計時攻擊(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計時攻擊?
Timing Attack 真的可行嗎?
我覺得各大語言的 API 都用這種實現,肯定還是有道理的,理論上應該可以被利用的。這不,學術界的這篇論文就宣稱用這種計時攻擊的方法破解了?OpenSSL 0.9.7?的RSA加密算法了。關于?RSA 算法的介紹可以看看之前本人寫的這篇文章。
這篇Remote Timing Attacks are Practical[3]?論文中指出(我大致翻譯下摘要,感興趣的同學可以通過文末鏈接去看原文):
計時攻擊往往用于攻擊一些性能較弱的計算設備,例如一些智能卡。我們通過實驗發現,也能用于攻擊普通的軟件系統。本文通過實驗證明,通過這種計時攻擊方式能夠攻破一個基于 OpenSSL 的 web 服務器的私鑰。結果證明計時攻擊用于進行網絡攻擊在實踐中可行的,因此各大安全系統需要抵御這種風險。
最后,本人畢竟不是專研安全方向,以上描述是基于本人的理解,如果有不對的地方,還請大家留言指出來,感謝。
總結
以上是生活随笔為你收集整理的这 10 行比较字符串相等的代码给我整懵了,不信你也来看看!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万万没想到,一个可执行文件原来包含了这么
- 下一篇: 以女朋友为例讲解 TCP/IP 三次握手