抱歉用這種標題吸引你點進來了,不過你不妨看完,看看可否讓你有所收穫。(有收穫,請評論區留個言,沒收穫,下週末我直播吃**,哈哈,這你也信)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:感謝正在閱讀文章的你,讓我還有動力繼續堅持更新原創。
本人發文很少,但但願寫的文章能達到的目的是:佔用你的閱讀時間,就儘可能可以讓你有所收穫。
若是你以爲個人文章有所幫助,還請你幫忙轉發分享,另外請別忘了點擊公衆號右上角加個星標,好讓你別錯事後續的精彩文章(微信改版了,或許我發的文章都不能推送到你那了)。
原創真心不易,但願你能幫我個小忙唄,若是本文內容你以爲有所啓發,有所收穫,請幫忙點個「在看」唄,或者轉發分享讓更多的小夥伴看到。 參考資料: