本文譯自http://www.muppetlabs.com/~breadbox/txt/rsa.html,作者Brian Raiter.
This article is translated from http://www.muppetlabs.com/~breadbox/txt/rsa.html. The author is Brian Raiter. Thank Brian very much for the great text & sharing spirit.
前言:我們將介紹什麼
研究素數理論的數學家們一直希望甚至可以說是渴望看到自己的研究能在數學領域之外派上用場, 而RSA加解密體系就是一個將抽象數學理論應用至現實世界的極好案例。引言:單向陷門函數(Trapdoor function)
如果X爲3,那麼F(X)就是64.所以"F(3)“就是"7*3+43"的簡寫。
對應C語言程序裏頭的函數可以寫成:
int F(int x)
{
return 7 * x + 43;
}
數學家們通常也很關心一個函數的反函數,比如
G(X)=(X-43)/7, G(64)=3.
用編程語言來表達,假設有下面兩個函數:
int foo(int x);
int bar(int x);
而且foo() 和 bar() 跟數學函數一樣,只是做一定計算然後返回一個數值,而且他們對於同樣的輸入參數總是輸出同樣的值。 (並且假設運行程序的計算機支持無限大的整數)。在此基礎上,我們假設bar()是foo()的反函數。也就是說以下命題在foo的調用參數合法的前提下總是成立。
x == bar(foo(x))
現在,假設你知道foo的源代碼,而不知道bar的。你覺得你可以寫出你自己的bar()函數實現嗎?
好像你應該可以,因爲foo()做了什麼你知道得一清二楚。你也可以隨便用不同的輸入調用foo()多少次都可以,並且你已經知道bar()一定存在。所以你覺得你可以寫出bar()的實現來,但我們能保證一定能夠重構出bar來 嗎?
理論上的答案是yes。給出一個foo()這樣的函數,總是可能構建出反函數來的。但是,如果我們在這個問題上再加個時間上的限制,就算是你必須在宇宙終結前找出來也行,答案可能就會發生變化。
存在一些特別的函數,它們做什麼看起來也很簡單,而且它們怎麼做到的也很清楚,但是找到它們的反操作則幾乎是不可能完成的任務。這種函數就是單向限門函數(Trapdoor function)。
1975年,Whitfield Diffie, Martin E. Hellman, and Ralph Merkle認識到某些Trapdoor function可以作爲一種新的密碼學的數學基礎。這種密碼學可以做到就算是加密方法被所有人知道,而解密方法還可以保持其祕密性。 Diffie&Hellman於1976年發佈了一篇論文描述了這個方法,並且提供了一些安全性比較弱的trapdoor functions。一年後的1977年,在一個MIT的技術備忘錄中,Ronald L. Rivest, Adi Shamir, and Leonard Adleman就指出了一種trapdoor function可以成爲RSA密碼學的基礎。 接下來我們就花點時間來描述一些這個函數。
背景第一節:指數計算
下面是指數運算的簡單複習,應該還記得N^2= N * N, N^3= N * N * N, N^4= N * N * N * N,
比如:
2^7 = 2 * 2 * 2 * 2 * 2 * 2 * 2 = 128.
我們也知道N的E次方再乘於N也就是N的E+1次方:
N^E * N = N^(E + 1).
2^7 * 2 = 128 * 2 = 256 = 2^8.
N^E * N * N = N^(E + 2).
N^E * N^2 = N^(E + 2).
N^A * N^B = N^(A + B).
那麼:
N^A * N^A = N^(2*A)=(N^A)^2
再推而廣之得到公式2:
(N^A)^B = N^(A * B).
這兩個公式在處理指數運算的時候非常有用。
背景第二節:模運算
幾乎所有計算機程序員都是通過取餘運算符(通常記爲%)而熟悉模運算的。取餘運算符計算出一個整數被另一個整數相除所得的餘數,而不是商。例如:27 % 12 = 3.
27 = 3 (mod 12), 注:數學上一般使用三橫槓的同餘符號。
27 同餘 3, 模 12.
11 + 16 = 3 (mod 12)
模運算有時也被稱爲鐘面運算。前面的運算式對應時鐘的運轉則表示,如果現在是11點,16小時後會變爲3點。
我們也可以發現,模運算的加法裏的數是可以被其同餘數替換的,比如:
因爲 16=4 (mod 12)
11 + 16 = 11 + 4 = 15=3 (mod 12).
編程語言裏的表示
(11+16) %12 = (11+4)%12 = 15 %12 = 3
又比如:
9835 = 7 (mod 12), 而 1176 = 0 (mod 12), 那麼 9835 + 1176 = 7 + 0 = 7 (mod 12).
9835 * 1176 = 7 * 0 = 0 (mod 12)(我們可以算出9835 * 1176=11565960, 而 11565960 = 0 (mod 12)).
如果模爲10,那麼對10求模則可以得到整數的個位數。例如:
37 = 7 (mod 10), 287 + 482 = 9 (mod 10), 那麼 895 * 9836 = 0 (mod 10).
可以想象,模運算可以使得很大的數字運算變得小數運算,使得我們不用去計算實際的數值只需要計算這些數的餘數就可以,從而保證在有寄存器大小限制的計算機中實現計算變得容易。
背景第三節:算術基本定理(Fundamental Theorem of Arithmetic)
算術基本定理表明對於所有大於1的自然數都存在且只有一種方式將其分解爲素數的乘積。比如1176 = 2 * 2 * 2 * 3 * 7 * 7, or 1176 = 2^3 * 3^1 * 7^2.
這個定理確保不存在其它方式將1176分解成不同的素數因子。 這個定理也是爲什麼不把1作爲素數之一的原因,因爲如果把1定義爲素數,那麼所有自然數的素因子分解將會有無限種方式了。
背景第四節:互質數
兩個數的最大公因子(縮寫爲GCD)定義爲可以整除兩個數的最大數。比如:GCD(15, 10) = 5, GCD(18, 10) = 2, GCD(21, 10) = 1, 而 GCD(170, 102) = 34.
另一種表述方式就是最大公因子是兩個數的素因子的交集裏面數的乘積:
GCD((2^3 * 3^1 * 7^2), (2^2 * 5^1 * 7^3)) = 2^2 * 7^2, 所以 GCD(1176, 6860) = 196.
如果兩個數沒有其它公因子,則GCD爲1,則我們說這兩個數是互質的。比如21和10就是互質的。
那麼所有素數跟除了它自己倍數的所有其它數都是互質的。
歐拉函數(Euler's Totient Function)
歐拉函數使用希臘字母phi(音爲phi)表示,定義爲:phi(4)= 2 (1 和 3 與4互質), phi(5)= 4 (1, 2, 3 和 4 與5互質), phi(6)= 2 (1 和 5 與 6互質), phi(7)= 6 (1, 2, 3, 4, 5 和 6 與 7互質), phi(8)= 4 (1, 3, 5 和 7與 8互質) phi(9)= 6 (1, 2, 4, 5, 7 和 8與 9互質).
如果我們用C語言表示
phi = 1;
for (i = 2 ; i < N ; ++i)
if (gcd(i, N) == 1)
++phi;
phi(P * Q) = (P - 1) * (Q - 1), if P和Q 是素數,P不等於Q。比如:
phi(15) = 2 * 4 = 8 (1, 2, 4, 7, 8, 11, 13, 和 14).
可以自己嘗試去證明(提示:從1至P*Q-1這些數中,不與P*Q互質的數是P和Q的倍數)。
當然,P和Q必須是不等的。例如phil(9)=6, 而不是phil(9)=phi(3) * phi(3)=2*2=4.
歐拉定理(Euler's Totient Theorem)
歐拉定理是RSA算法的核心理論之一:如果GCD(T, R) = 1 而且 T < R, 那麼 T^(phi(R)) = 1 (mod R).
如果T和R互質,且T<R, 那麼T的phi(R)次方對R求餘數,結果爲1.
phi(6) = (2 - 1) * (3 - 1) = 1 * 2 = 2, 那麼 5^(phi(6)) = 5^2 = 25, 25 = 24 + 1 = 6 * 4 + 1, 得到 25 = 1 (mod 6).
phi(15) = (3 - 1) * (5 - 1) = 2 * 4 = 8, 那麼 2^(phi(15)) = 2^8 = 256, 而且 256 = 255 + 1 = 17 * 15 + 1, 得到 256 = 1 (mod 15).
推導變化
如果GCD(T, R) = 1 而且 T < R, 那麼 T^(phi(R)) = 1 (mod R).
T^(phi(R)) * T^(phi(R)) = 1 * 1 (mod R),
T^(phi(R) + phi(R)) = 1 * 1 (mod R),
T^(2 * phi(R)) = 1 (mod R).
T^(3 * phi(R)) = 1 (mod R).
很明顯如果我們繼續下去,我們可以得到一個歐拉定理的推廣的表示方式
可以注意到K*phi(R)表示的是phi(R)的倍數,也就是可以被phi(R)的整除的數。用模運算的語言來說,K*phi(R)對phi(R)求模與0同餘。
那麼:
如果GCD(T, R) = 1且 T < R,如果又有 S = 0 (mod phi(R))成立,則T^S = 1 (mod R)
T^(S + 1) = T (mod R).
T^(S + 1) * T = T * T (mod R),
T^(S + 2) = T^2 (mod R).
T^(S + 3) = T^3 (mod R),
類似地我們可以得出:
T^(S+K)=T^K(mod R),S = 0 (mod phi(R))
也就是說我們可以得到
T^E = T^F (mod R),當E = F (mod phi(R)).(再次強調,T < R, 而且 T 和R互質).
重點
我們現在就要到達最重要的部分了!我們再來仔細看看上面的一個等式:T^(S + 1) = T (mod R). 【公式3】
P * Q = S + 1,
對於某個S的可能值成立(回顧一下,S = 0 (mod phi(R) ).
或者,更加直接的表示方式是
P * Q = 1 (mod phi(R))
T^(P * Q) = T (mod R),
(T^P)^Q = T (mod R)
而這個式子是可以拆分爲兩個步驟完成的:
T^P = X (mod R), 那麼 X^Q = T (mod R).
好了,如果你還沒有看出這個方法的價值,我們來想象一下這兩個步驟是由兩臺獨立的計算機完成的,而X是由第一臺計算機通過一條不安全的電話線通道發送到第二臺計算機的...
這真的有用嗎?
T可以是準備發送的明文消息,P、Q和R作爲加密的祕鑰,進一步P和R作爲公鑰,而Q和R作爲私鑰。那麼X則是被加密的信息。我們再來看關於P和Q的最重要的等式。
P * Q = 1 (mod phi(R))
那麼可以觀察到P和Q必然與phi(R)互質,否則如果P或者Q與phi(R)有一個公因子D(D>1),那麼D也將會是P*Q與phi(R)的公因子,那麼
gcd(P*Q, phi(R))=D, 假設P*Q=D*M, phi(R)=D*N,M與N互質。
我們知道
P*Q = k*phi(R) + 1,
那麼
D*M = k* D*N+1, 有 D*(M-k*N) = 1。
而D>1, 以上式子顯然不可能成立,所以 P與Q必然與phi(R)互質,這個很重要。
想象一個只有時針的鐘,時針一開始指向12點整,我們讓它跳着走,每次跳N小時。如果N是可以被2或者3整除(2和3是12的素數因子),那麼我們可以發現時針在回到12點前總是指向一部分固定的刻度。比如N=2,時針將會按照這些步驟重複:12,2,4,6,8,10,12,2,4,6,8,10,12…
但如果N是與12互質的數,那麼時針在回到12點前會覆蓋所有刻度。比如N=7,時針的走法將是12,7,2,9,4,11,6,1,8,3,10,5,12,...
類似地,P,Q與phi(R)互質是非常重要的。因爲如此,我們可以保證對每個可能的T值,只要把它作指數爲P的冪運算,再對R求模,總是會得到不同的X值(記住當作冪運算後的求模運算的話,一個循環的長度是由phi(R)決定的)。
選擇密鑰
我們先隨便挑選兩個素數U和V,然後算出乘積作爲R。爲什麼要這樣做,因爲這樣我們很方便可以計算得出 phi(R)=(U-1)*(V-1)。
其次越少因數使得分解過程變得越複雜和越費時。然後我們找到兩個數P和Q使得
P * Q = 1 (mod phi(R)).
我們就會將P和R作爲公鑰,Q和R作爲私鑰。選定這些數後U和V就可以丟掉了。
實例
我們假設U=5,V=11,那麼R=55,phi(55) = (5 - 1) * (11 - 1) = 4 * 10 = 40。
然後我們需要找出P和Q使得 P * Q = 1 (mod 40). 當然,符合這個條件的P和Q有無窮多個。這裏我們嘗試找出其中一對就可以了。
第一個可以幫我們省時間的是P和Q必須和40互質,也就是說P和Q因子都不能含有2和5。
然後P不能與Q對40同餘。根據T^E = T^F (mod R)(當E = F (mod phi(R))),會有T^P=T^Q。而P是R都是公鑰,那隻要找個Q=P(mod phi(R)),就可以根據(T^P)^Q=(T^Q)^Q =T(mod R)反推出T了。也就是使得RSA退化成了對稱加密算法了,因爲在這種情況下P不能被泄露。一般來說,使P和Q互質是最佳選擇。我們從P=7開始,根據下面的等式來找Q。
7 * Q = 1 (mod 40).
也就是
7 * Q = K * 40 + 1, K爲任意自然數。
7 * 23 = 161 = 4 * 40 + 1.
現在我們有P=7作爲公鑰,Q=23作爲私鑰。
根據我們的加密算法,傳送的信息T必須與R小,而且要與R互質。當然T不能爲1,因爲1無論作任何冪運算模運算最後結果都是1。類似地R-1也需要排除,因爲R-1與-1對R=55同餘。那麼我們把剩下的一些數製做出以下的字符表:
2 3 4 6 7 8 9 12 13 14 16 17 18
我們假設要傳送的信息爲"VENIO" (拉丁語的 "I come"):
V E N I O
對這些數作指數爲P的冪運算然後對R作模運算。
V:31^7 (mod 55) =27512614111 (mod 55) =26
所以加密後的消息爲 26, 28, 24, 7, 21,也就是在字符表裏的 "RTQEO"。
當"RTQEO"在對端被收到後,用這些數作指數爲Q的冪運算然後對R作模運算就得到T值。
R:26^23 (mod 55) = 350257144982200575261531309080576 (mod 55) =31
也就是 31, 7, 19, 13, 21= "VENIO"。
怎麼破解RSA
假設我們已經竊聽到"RTQEO",P=7和R=55也從公鑰目錄中被找到,我們在Q未知的情況下怎麼將這個消息破解。我們知道 P * Q = 1 (mod phi(R)),或者,
P * Q = K * phi(R) + 1.
Q = (K * phi(R) + 1) / P.
K也是未知,那我們可以暴力測試。
(1 * 40 + 1) / 7 = 41 / 7 (不能整除)
每次我們找到一個可能值,就對消息進行測試,如果得到不合理的破解,我們可以繼續嘗試。如果23不合適,我們可以繼續窮舉K進行測試。因爲P爲7,很明顯我們只需要每給7個數執行測試纔有可能得到能被7整除的數。再者,因爲P*Q是對phi(R)=40求模,那麼Q也只需要嘗試到39就可以了,所以看起來破解還是很簡單和很快的。 因爲R是公鑰,所以phi(R)也是公開的,而且我們知道R是由兩個素數乘積構成的,只要能夠找到這兩個素數就可以破解加密後的消息。那怎麼辦呢?
如何使RSA不能被破解
在上面的例子裏,因爲R比較小,所以暴力破解是很容易的。(當然因爲我們一次加密一個字符,也有可能被採用一些統計方法被破解)。但是隻要我們挑足夠大的U和V來生成R。比如我們不用5和11,而是用673和24971,那麼R會是16805483,phi(R)會等於16779840。(R值比較大也允許我們一次加密多個字節)尋找P和Q也不是你能夠用紙筆簡單能算出來的了,我用Perl從寫腳本到計算出來花了不到3分鐘找到可用的397和211333。但是,用計算機還是可以在幾秒內從16805483找到673和24971。所以即使是看起來夠大的數,仍然是不堪一擊。所以我們需要大得多的數!
當然,找到難於做素數分解的大數肯定不是一個問題,畢竟自然數可以是無限的。但是,考慮到我們要做冪運算,那麼大的數能用嗎?
極大指數冪值的模運算
問題在於,R越大,P和Q就會越大,而我們知道P和Q會作爲指數使用!下面這個數看起來很一般:9^(9^9)
但它的結果有3億5千萬位長!在加密數據之前,得找到個辦法來存儲幾T的數據才行。當然,如果是這樣的話,這個加密方法也就無法使用了。
還好我們最後的結果是做模運算等到的,模運算總是能夠把數明顯變小。
看回我們之前的例子,我們現在要解密28,因爲R=55,Q=23,所以我們要知道的是
28^23 (mod 55) = ?
23 = 16 + 4 + 2 + 1, or 23 = 2^4 + 2^2 + 2^1 + 2^0.
28^23 = 28^(2^4 + 2^2 + 2^1 + 2^0)
這個第一眼看起來還是不怎麼樣。但是仔細觀察還是可以發現很多運算重複了。加上最後是需要做模運算的,那麼我們可以利用前面討論過的規律先計算好這些重複計算部分的摸運算結果。
比如:
28^2 = 784 = 14 (mod 55).
28^23 = (((28^2)^2)^2)^2 * (28^2)^2 * 28^2 * 28 (mod 55),
28^23 = ((14^2)^2)^2 * 14^2 * 14 * 28 (mod 55).
14^2 = 196 = 31 (mod 55),
28^23 = (31^2)^2 * 31 * 14 * 28 (mod 55).
31^2 = 961 = 26 (mod 55), and 26^2 = 676 = 16 (mod 55);
28^23 = 16 * 31 * 14 * 28 (mod 55).
28^23 = 16 * 31 * 14 * 28 (mod 55)
看回下面的例子,結果7跟我們直接計算指數得到的值是一模一樣的。利用上面的方式,我們需要處理的數不會大過(R - 1)^2.
怎麼找到很大的P和Q
神奇的模運算也可以保證我們可以找到想要的P和Q。回顧一下我們需要找到符合下面條件的P和QP * Q = 1 (mod phi(R)),也就是說
P * Q = K * phi(R) + 1, K可以爲任何自然數。
當我們選擇合適的P後,當然不是我們的例子裏頭的7那麼小的數,然後我們需要找到Q,重寫以上的等式:
P * Q - phi(R) * K = 1,
其中P和phi(R)爲已知數,這個等式被稱爲Diophantine等式。當然我們在這裏只是告訴讀者已經存在一些能在很短時間內求出Q的算法,不再詳細闡述。
數字裏的安全
我們已經知道RSA的基本原理,其中很關鍵的是要選擇一個合適的R。R必須是兩個素數的乘積,而且必須要足夠大使得它很難做素數分解。那麼我們要怎麼樣開始來尋找這些數呢?我們知道判斷一個數是不是素數是一個相對簡單的事情。其中一個最著名的方法稱爲費馬小定理( Fermat's Little Theorem),下面是其中一種適合我們使用的表示形式:
如果P是素數,那麼N^(P-1)=1(mod P)對於所有N<P都成立。
這個定理是不是讓你聯想起歐拉定理(Euler's Totient Theorem)?不奇怪,歐拉是第一個發佈費馬小定理證明的,而他的歐拉定理是費馬小定理的一種推廣。如果你記得對於素數P,phi(P)=P-1,那麼費馬小定理就變得容易理解了。
當然,這個定理只有在證明一個數是合數時才比較有用。比如
3^14 (mod 15) = 4782969 (mod 15) = 9, 和
3^16 (mod 17) = 43046721 (mod 17) = 1,
也就是說如果我們想知道一個數是否是素數,我們可以從2開始作類似計算,只要有一個結果不是1,那麼那個數就是合數。如果我們從2到P-1都得到1,那麼這個數一定是素數嗎?很不幸,答案是否定的。存在一些數叫做Carmichael數,他們雖然比素數少,但也存在無窮多個,都可以通過以上的測試。所以我們不能只是依賴這個測試。
幸運地是,我們還有其它可靠方法用來證明一個數是否爲素數。但是任何方法也只能證明一個數可以被分解,但是可以分解爲什麼數對於大數來說是十分困難的。
總結
基本的事實是,爲了找到一個合數的因子,我們也只能堅持用暴力破解的方式:將這個數字除以所有的素數,直到其中一個看起來是合適的。有很多方法來改善這種方法(數域篩選(Number Field Sieve)目前是最好的),但他們是複雜的,他們做的是讓你縮小搜索範圍。他們不足以使這個問題變得可以解決。 而且可以預見新的分解方法仍然不能把問題簡化到容易解決!真正的問題是,加密和解密的算法的運行時間對於R的長度是線性的。也就是說,R中的數字的位數增加一倍也使得加解密的需要的時間加倍,選擇兩個素數P和Q的時間也一樣。但探索R的素因子的算法的運行時間是隨R的位數長度呈指數增長。
因此,如果出現一個新的技術,使得可以以一萬億倍的速度分解一個數。我們需要做的是增加R的大小,又會使得破解變得困難。當然,我們也會需要多一點的時間來發送和接收我們的信息。已經有一些人在用一些祕鑰就算是用Number Field Sieve來破解,需要的能量比宇宙中已知的能量還要多。
一個例子,在寫這篇文章的時候,被分解的最大的數是一個在RSA-140 挑戰中使用的模,是1999年2月2日完成的。在此之前的記錄是一個RSA-130的數,將它分解動用了總共1000 MIPS-年 的計算時間。 RSA-140使用的模只需要10位數長,需要的時間估計會達到兩倍。
最後,這是什麼使得RSA稱爲陷門函數的核心因素:那就是從兩個素數得到一個數和從一個數重新分解出兩個素數的難度差別非常大。
如果找到一個可以快速分解素因子的快速算法,將徹底摧毀RSA的加密算法。這種可能性還沒有被證明是不可能的,當然也很可能將永遠也不會被發現。但是,考慮素數已研究了數千年,這個問題在過去的幾十年中再次受到重視,這樣的算法存在的可能性似乎微乎其微。發現這個算法將會極大改變數論的面貌,就像RSA使得加密算法大爲改觀。
但是 - 如果這種情況發生,也會有其他陷門函數在那裏等待被發現。無論RSA的未來怎麼樣,限門函數已經永遠改變了加密學!