RSA 算法一種常見的非對稱加密算法, 經常使用來對一些在網絡上傳輸的敏感信息進行加密, 本文將概述RSA算法的流程以及一種意想不到的"旁門左道"的攻擊方式. html
RSA 算法流程以下, java
p
和 q
, 計算 N = p*q
e
, 使得 e
與 (p-1)(q-1)
互質, 此時公鑰爲 (N, e)
, 告訴給對方d
, 使得 e*d-1
可以被(p-1)(q-1)
整除M
, 加密密文C
爲: $C = M^e \mod N$C
, 解密消息 M
: $M= C^d \mod N$RSA算法依賴於歐拉定理, 一個簡化版本爲大體爲 a
和 p
互質, 那麼有, linux
$ a^{p-1} \equiv 1 \mod p$, a
的 p-1
次方 對 p
取餘爲1
, (a
的 p-1
次方減去1
能夠整除 p
).算法
歐拉定理的證實比較複雜,原本有一個絕妙的證實方式的, 但因爲微信公衆號字數有限, 這裏就省略了(什麼? 這跟費馬有什麼關係? 實在要看的能夠看文末參考資料)編程
舉個例子安全
N = pq
, 取倆素數 p=11, q = 3, N = p * q = 33
, 取 e
與 (p-1)(q-1) = 20
互質的數 e = 3
, 而後經過 $ed \equiv 1 \mod (p-1)(q-1)$ 肯定私鑰,
即取一個 d
使得 3*d -1
能 20 被整除, 假設取 d=7
或者d=67
. (3*7-1=20
固然能被20整除, 3*67-1=200
也能被20整除)微信
所以 public key 爲 (N=33, e=3)
, private key 爲 d=7
或者d=67
,
假設加密消息M=8
,
經過加密算法 $C = M^e \mod N$
獲得密文 C=8^3 % 33 = 17
cookie
再來看解密, 由$M= C^d \mod N$, 獲得明文 M = 17^7 % 33 = 8
或者 M=17^67 % 33=8
, 是否是很神奇? (這裏^
表示多少次方, 後文中的有的表示異或)網絡
(來, 安利一個計算器的工具, bc
命令, 支持任意精度的計算, 其實 Mac簡單的計算就能夠經過前面介紹的 Alfred 能夠方便得完成)
若是須要破解 RSA 的話, 就是須要找到 p
和 q
, 使得 pq=33
, 若是知道了 p
和 q
就能經過公鑰 N
和 e
反推出私鑰 d
了. 然而大數分解在歷史以來就一直是數學上的難題.
固然上面所述的案例較簡單, 當 N 很大時, 就特別困難了. 曾經有人花了五個月時間分解了這個數39505874583265144526419767800614481996020776460304936454139376051579355626529450683609727842468219535093544305870490251995655335710209799226484977949442955603
(159位數), RSA-155 (512 bits) [from wikipedia].
這條路走不通, 就有人走了"旁門左道"了, Stanford 的幾個研究者用了兩個小時破解了 OpenSSL 0.9.7 的 1024-bit 的 RSA 私鑰 (感興趣的同窗能夠看他們的論文Remote Timing Attacks are Practical),
用到的方法就是後面提到的時序攻擊(或譯爲"計時攻擊"), 主要思想是由於在進行加密時所進行的模指數運算是一個bit一個bit進行的, 而bit爲1所花的運算比bit爲0的運算要多不少(耗時久),
所以能夠經過獲得大量消息與其加密時間, 而後基於統計的方法就能夠大體反推出私鑰的內容.
計時攻擊是邊信道攻擊(或稱"側信道攻擊", Side Channel Attack, 簡稱SCA) 的一種, 主要是一種利用不一樣的輸入會有不一樣的執行時間這個特色.
舉個具體的例子, 這個來自playframewok 裏用來驗證cookie(session)中的數據是否合法(包含簽名的驗證), 也是我寫這篇文章的由來.
def safeEquals(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
, 若是每一 bit 都相等的話, 兩個字符串確定相等, 最後的equal
確定爲0, 不然爲1.
但從效率角度上講, 不是應該只要中途發現某一位的結果爲1了就能夠當即返回 false 了嗎? (以下所示)
for (i <- Array.range(0, a.length)) {
if (a(i) ^ b(i) != 0) // or a(i) != b[i]
return false
}複製代碼
結合方法名稱 safeEquals
可能知道些眉目, 與安全有關, 延遲計算等提升效率的手段見過很多, 但這種延遲返回的仍是不多見.
這種手段可讓調用 safeEquals("abcdefghijklmn", "xbcdefghijklmn")
和調用 safeEquals("abcdefghijklmn", "abcdefghijklmn")
的所耗費的時間同樣,
防止經過大量的改變輸入並經過統計運行時間來暴力破解出要比較的字符串, 這裏其實都忽略了對比較字符串長度的attack問題.
舉個例子, 假設某個用戶設置了密碼爲 password
, 經過從a到z(實際範圍可能更廣)不斷枚舉第一位, 最終統計發現 p0000000
的運行時間比其餘從任意a~z
的都短,
這樣就能猜想出用戶密碼的第一位極可能是p
, 而後再不斷一位一位迭代下去最終破解出用戶的密碼. 若是密碼經過hash加密後也能經過這種攻擊方式獲得hash後的密文.
固然, 從理論角度上講這個確實容易理解, 如上文所提到的學術界已經有論文發表指出用這種計時攻擊的方法破解了 OpenSSL 0.9.7 的RSA加密算法了.
然而在實際中是否存在這樣的攻擊問題呢?
由於好像經過統計運行時間總感受不太靠譜, 這個運行時間對環境太敏感了, 好比網絡, 內存, CPU負載等等都會影響.
儘管如此, 各個軟件的實現也都採用了這種 safeEquals
的方法.
JDK 1.6.0_17
中的Release Notes中就提到了MessageDigest.isEqual
中的bug的修復
BugId | Category | Subcategory | Description |
---|---|---|---|
6863503 | java | classes_security | SECURITY: MessageDigest.isEqual introduces timing attack vulnerabilities |
此次變動的diff詳細信息來源爲:
爲了防止(特別是與簽名/密碼驗證等相關的操做)被 timing attack, 目前各大語言都提供了響應的安全比較函數, 例如 "世界上最好的編程語言" -- 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來肯定兩個字符串是否相等.
參考資料:
p.s 若是你以爲這文章對你有那麼一點點收穫, 請不要猶豫掃描下面二維碼關注個人公衆號, 若是你再能幫忙轉發一下就更好了. 麼麼噠.