?關于使用python實現RSA加密解密
一、非對稱加密算法
1、乙方生成兩把密鑰(公鑰和私鑰)。公鑰是公開的,任何人都可以獲得,私鑰則是保密的。
2、甲方獲取乙方的公鑰,然后用它對信息加密。
3、乙方得到加密后的信息,用私鑰解密。
二、RSA算法
1977年,三位數學家Rivest、Shamir 和 Adleman 設計了一種算法,可以實現非對稱加密。這種算法用他們三個人的名字命名,叫做RSA算法。從那時直到現在,RSA算法一直是最廣為使用的"非對稱加密算法"。毫不夸張地說,只要有計算機網絡的地方,就有RSA算法。
這種算法非常可靠,密鑰越長,它就越難破解。根據已經披露的文獻,目前被破解的最長RSA密鑰是768個二進制位。也就是說,長度超過768位的密鑰,還無法破解(至少沒人公開宣布)。因此可以認為,1024位的RSA密鑰基本安全,2048位的密鑰極其安全。
三、數學基礎
1、互質關系
如果兩個正整數,除了1以外,沒有其他公因子,我們就稱這兩個數是互質關系(coprime)。比如,15和32沒有公因子,所以它們是互質關系。這說明,不是質數也可以構成互質關系。
關于互質關系,不難得到以下結論:
1.任意兩個質數構成互質關系,比如13和61。
2.一個數是質數,另一個數只要不是前者的倍數,兩者就構成互質關系,比如3和10。
3.如果兩個數之中,較大的那個數是質數,則兩者構成互質關系,比如97和57。
4. 1和任意一個自然數是都是互質關系,比如1和99。
5. p是大于1的整數,則p和p-1構成互質關系,比如57和56。
6. p是大于1的奇數,則p和p-2構成互質關系,比如17和15。
2、歐拉函數
請思考以下問題:
任意給定正整數n,請問在小于等于n的正整數之中,有多少個與n構成互質關系?(比如,在1到8之中,有多少個數與8構成互質關系?)
計算這個值的方法就叫做歐拉函數,以φ(n)表示。在1到8之中,與8形成互質關系的是1、3、5、7,所以 φ(n) = 4。
φ(n) 的計算方法并不復雜,但是為了得到最后那個公式,需要一步步討論。
[if !supportLists]四、[endif]密鑰生成
我們通過一個例子,來理解RSA算法。假設愛麗絲要與鮑勃進行加密通信,她該怎么生成公鑰和私鑰呢?
第一步,隨機選擇兩個不相等的質數p和q。
愛麗絲選擇了61和53。(實際應用中,這兩個質數越大,就越難破解。)
第二步,計算p和q的乘積n。
愛麗絲就把61和53相乘。
n = 61×53 = 3233
n的長度就是密鑰長度。3233寫成二進制是110010100001,一共有12位,所以這個密鑰就是12位。實際應用中,RSA密鑰一般是1024位,重要場合則為2048位。
[if !supportLists]l?[endif]第三步,計算n的歐拉函數φ(n)。
根據公式:
φ(n) = (p-1)(q-1)
愛麗絲算出φ(3233)等于60×52,即3120。
第四步,隨機選擇一個整數e,條件是1< e < φ(n),且e與φ(n) 互質。
愛麗絲就在1到3120之間,隨機選擇了17。(實際應用中,常常選擇65537。)
第五步,計算e對于φ(n)的模反元素d。
所謂"模反元素"就是指有一個整數d,可以使得ed被φ(n)除的余數為1。
ed≡ 1 (mod φ(n))
這個式子等價于
ed - 1 = kφ(n) ?(k∈Z)
于是,找到模反元素d,實質上就是對下面這個二元一次方程求解。
ex +φ(n)y = 1
已知e=17,φ(n)=3120,
17x + 3120y = 1
這個方程可以用"擴展歐幾里得算法"求解,此處省略具體過程。總之,愛麗絲算出一組整數解為 (x,y)=(2753,-15),即 d=2753。
至此所有計算完成。
第六步,將n和e封裝成公鑰,n和d封裝成私鑰。
在愛麗絲的例子中,n=3233,e=17,d=2753,所以公鑰就是 (3233,17),私鑰就是(3233, 2753)。
總結,實際上就是計算n,e,d的過程
pq的作用用于求n==pq,再用 (p-1)(q-1)求φ(n),在φ(n)范圍內隨機選擇即為e,d==e對于φ(n)的模反元素
五、驗證RSA算法的可靠性
公鑰公開,私鑰不公開,故d被破解即RSA算法被破解。
回顧上面的密鑰生成步驟,一共出現六個數字:
p,q,n,φ(n),e,d
這六個數字之中,公鑰用到了兩個(n和e),其余四個數字都是不公開的。其中最關鍵的是d,因為n和d組成了私鑰,一旦d泄漏,就等于私鑰泄漏。
那么,有無可能在已知n和e的情況下,推導出d?
ed=1 (modφ(n))。只有知道e和φ(n),才能算出d。
φ(n)=(p-1)(q-1)。只有知道p和q,才能算出φ(n)。
n=pq。只有將n因數分解,才能算出p和q。
結論:如果n可以被因數分解,d就可以算出,也就意味著私鑰被破解。
可是,大整數的因數分解,是一件非常困難的事情。目前,除了暴力破解,還沒有發現別的有效方法。維基百科這樣寫道:"對極大整數做因數分解的難度決定了RSA算法的可靠性。換言之,對一極大整數做因數分解愈困難,RSA算法愈可靠。
假如有人找到一種快速因數分解的算法,那么RSA的可靠性就會極度下降。但找到這樣的算法的可能性是非常小的。今天只有短的RSA密鑰才可能被暴力破解。到2008年為止,世界上還沒有任何可靠的攻擊RSA算法的方式。
只要密鑰長度足夠長,用RSA加密的信息實際上是不能被解破的。"
舉例來說,你可以對3233進行因數分解(61×53),但是你沒法對下面這個整數進行因數分解。
1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413
它等于這樣兩個質數的乘積:
3347807169895689878604416984821269081770479498371376856892431388982883793878002287614711652531743087737814467999489
×
36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
事實上,這大概是人類已經分解的最大整數(232個十進制位,768個二進制位)。比它更大的因數分解,還沒有被報道過,因此目前被破解的最長RSA密鑰就是768位。
[if !supportLists]六、[endif]加密與解密
有了公鑰和密鑰,就能進行加密和解密了。
1、加密要用公鑰 (n,e)
假設鮑勃要向愛麗絲發送加密信息m,他就要用愛麗絲的公鑰 (n,e) 對m進行加密。這里需要注意,m必須是整數(字符串可以取ascii值或unicode值),且m必須小于n。
所謂"加密",就是算出下式的c:
m^e≡ c (mod n)
愛麗絲的公鑰是(3233, 17),鮑勃的m假設是65,那么可以算出下面的等式:
65^17≡ 2790 (mod 3233)
于是,c等于2790,鮑勃就把2790發給了愛麗絲。
2、解密要用私鑰(n,d)
愛麗絲拿到鮑勃發來的2790以后,就用自己的私鑰(3233, 2753) 進行解密。可以證明,下面的等式一定成立:
c^d≡ m (mod n)
也就是說,c的d次方除以n的余數為m。現在,c等于2790,私鑰是(3233, 2753),那么,愛麗絲算出
2790^2753≡ 65 (mod 3233)
因此,愛麗絲知道了鮑勃加密前的原文就是65。
至此,"加密--解密"的整個過程全部完成。
我們可以看到,如果不知道d,就沒有辦法從c求出m。而前面已經說過,要知道d就必須分解n,這是極難做到的,所以RSA算法保證了通信安全。
你可能會問,公鑰(n,e)只能加密小于n的整數m,那么如果要加密大于n的整數,該怎么辦?有兩種解決方法:一種是把長信息分割成若干段短消息,每段分別加密;另一種是先選擇一種"對稱性加密算法"(比如DES),用這種算法的密鑰加密信息,再用RSA公鑰加密DES密鑰。
七、私鑰解密的證明
a=b(mod c)等價于a/c的余數是b,a mod c ==b
最后,我們來證明,為什么用私鑰解密,一定可以正確地得到m。也就是證明下面這個式子:
c^d≡ m (mod n)
因為,根據加密規則
m^e ≡ c (mod n)
于是,c可以寫成下面的形式:
c = m^e - kn(h∈Z)
將c代入要我們要證明的那個解密規則:
由于(a-b)^n=a^n-C1n a^(n-1)b+C2n a^(n-2)b^2+...+(-b)^n
它等同于求證
m^(ed) ≡ m (mod n)
由于
ed ≡ 1 (mod φ(n))
所以
ed = hφ(n)+1
將ed代入:
m^(hφ(n)+1) ≡ m (mod n)
接下來,分成兩種情況證明上面這個式子。
當m與n互質。
根據歐拉定理,此時
=====> m^φ(n)=kn+1 (k∈Z)
得到
證明(kn+1)^h*m=m(mod n)展開即可
原式得到證明。
當m與n不是互質關系。
此時,由于n等于質數p和q的乘積,所以m必然等于kp或kq。
以m = kp為例,考慮到這時k與q必然互質,則根據歐拉定理,下面的式子成立:
(kp)^q-1 ≡ 1 (mod q)
進一步得到
即
將它改寫成下面的等式
這時t必然能被p整除,即 t=t'p
因為m=kp,n=pq,所以
原式得到證明。
[if !supportLists]八、[endif]快速冪模算法
在講解快速冪取模算法之前,我們先將幾個必備的知識
1.對于取模運算:
(a*b)%c=(a%c)*(b%c)%c ?
這個是成立的:也是我們實現快速冪的基礎
核心思想在于:
將大數的冪運算拆解成了相對應的乘法運算,利用上面的式子,始終將我們的運算的數據量控制在c的范圍以下,這樣我們可以客服樸素的算法的缺點二,我們將計算的數據量壓縮了很大一部分,當指數非常大的時候這個優化是更加顯著的,我們用Python來做一個實驗來看看就知道我們優化的效率有多高了
算法實現:
#快速冪模運算,把b拆分為二進制,遍歷b的二進制,當二進制位為0時不計入計算
def quick_pow_mod(a, b, c):
????a = a % c
????ans = 1
#這里我們不需要考慮b<0,因為分數沒有取模運算
????while b != 0:
#判斷b的二進制最后一位數是不是1,是則參與計算
????????if b & 1:
????????????ans = (ans * a) % c
# ans = (ans * a) % c,理論上等價于 ans = (ans % c) * (a % c)但是不知道為什么這樣寫會出錯。已解決,因為可能最后一次相乘的時候返回一個未除盡的數
#相當于遍歷二進制的b
????????b >>= 1
# A(n) == A(n-1)^2,% c可以提高效率
????????a = (a % c) * (a % c)
????return ans
我們現在來看核心原理:
對于任何一個整數的模冪運算
a^b%c
對于b我們可以拆成二進制的形式 ?
b=b0+b1*2+b2*2^2+...+bn*2^n ?
這里我們的b0對應的是b二進制的第一位(倒數第一位),那么我們的a^b運算就可以拆解成
a^b0*a^b1*2*...*a^(bn*2^n) ?
對于b來說,二進制位不是0就是1,那么對于bx為0的項我們的計算結果是1就不用考慮了,我們真正想要的其實是b的非0二進制位,那么假設除去了b的0的二進制位之后我們得到的式子是
a^(bx*2^x)*...*a(bn*2^n) ?
這里我們再應用我們一開始提到的公式,那么我們的a^b%c運算就可以轉化為
(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
這樣的話,我們就很接近快速冪的本質了。
(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
我們會發現令
A1=(a^(bx*2^x)%c) ?
... ?
An=(a^(bn*2^n)%c) ?
這樣的話,假設bx都=1,An始終等于A(n-1)的平方,依次遞推。
首先,我們會觀察到,我們每次都是將b的規模縮小了2倍。
那么很顯然,原本的樸素的時間復雜度是O(n)。快速冪的時間復雜度就是O(logn)。在數據量越大的時候,者中優化效果越明顯。
[if !supportLists]九、[endif]Miller-Rabin素性測試算法
素性測試(即測試給定的數是否為素數)是近代密碼學中的一個非常重要的課題。雖然Wilson定理(對于給定的正整數n,n是素數的充要條件為)給出了一個數是素數的充要條件,但根據它來素性測試所需的計算量太大,無法實現對較大整數的測試。目前,盡管高效的確定性的素性算法尚未找到,但已有一些隨機算法可用于素性測試及大整數的因數分解。下面描述的Miller-Rabin素性測試算法就是一個這樣的算法。
算法:
首先要知道費馬定理只是n是素數的必要條件。即費馬定理不成立,n一定是合數;費馬定理成立,n可能是素數。接下來請看Miller-Rabin算法的分析過程。
x^2 = 1(mod p),p為質數,x小于p
x = 1或 p -1
x的偶數次方對p取余數,結果可能是1^x * (p-1)^y對p取余數,即結果有可能是1,p-1,或(p-1)^k對p取模,當k為偶數時=1,當k為奇數時=p-1
若有解,3/4概率是質數
算法實現:
# n為要檢驗的大質數,a < n,k = n - 1
def miller_rabin_witness(a, n):
????if n == 1:
????????return False
????if n == 2:
????????return True
# n - 1 = m * 2^q求解 m, q,因為n為偶數,所以必有解
????k = n - 1
# 2為 底數,n為N
????q = int(math.floor(math.log(k, 2)))
????while q > 0:
????????m = k / 2 ** q
#必須同時滿足兩個條件,因為m有可能是未除盡的數
????????if k % 2 ** q == 0 and m % 2 == 1:
????????????break
????????q = q - 1
#先計算 a ^ (n-1) == 1 mod(n) 是否成立,不成立必定為合數
????if quick_pow_mod(a, n - 1, n) != 1:
????????return False
#計算第一項
????b1 = quick_pow_mod(a, m, n)
????for i in range(1, q + 1):
????????if b1 == n - 1 or b1 == 1:
????????????return True
#后一項等于前一項的平方mod n
????????b2 = b1 ** 2 % n
????????b1 = b2
????if b1 == 1:
????????return True
????return False
# Miller-Rabin素性檢驗算法,檢驗8次
def prime_test_miller_rabin(p, k):
????while k > 0:
????????a = random.randint(1, p - 1)
????????if not miller_rabin_witness(a, p):
????????????return False
????????k = k - 1
????return True
十、[endif]擴展歐幾里德算法
擴展歐幾里德算法是用來在已知a, b求解一組x,y,使它們滿足貝祖等式: ax+by = gcd(a, b) =d(解一定存在,根據數論中的相關定理)。
e取65537,故list[0] * s + list[1] * e = 1,list[1]為(e)mod(s)的乘法逆元,也就是e對于φ(n)的模反元素d,此方程必有解。
歐幾里德算法停止的狀態是:a= gcd, b = 0 ,那么,這是否能給我們求解 x y 提供一種思路呢?因為,這時候,只要 a = gcd 的系數是 1 ,那么只要 b 的系數是 0 或者其他值(無所謂是多少,反正任何數乘以 0 都等于 0 但是a 的系數一定要是 1),這時,我們就會有: a*1 + b*0 = gcd
當然這是最終狀態,但是我們是否可以從最終狀態反推到最初的狀態呢?
假設當前我們要處理的是求出a和 b的最大公約數,并求出 x 和 y 使得 a*x + b*y= gcd ,而我們已經求出了下一個狀態:b 和 a%b 的最大公約數,并且求出了一組x1 和y1 使得: b*x1 + (a%b)*y1 = gcd , 那么這兩個相鄰的狀態之間是否存在一種關系呢?
我們知道:a%b = a - (a/b)*b(這里的 “/” 指的是整除,例如 5/2=2 , 1/3=0), 代入b*x1 + (a%b)*y1 = gcd 那么,我們可以進一步得到:
gcd = b*x1 + (a-(a/b)*b)*y1
= b*x1 + a*y1– (a/b)*b*y1
= a*y1 + b*(x1– a/b*y1)
對比之前我們的狀態:求一組x和 y 使得:a*x + b*y = gcd ,是否發現了什么?
這里:
x = y1
y = x1– a/b*y1
算法實現:
#這里的邏輯很復雜
#擴展歐幾里得算法,得到結果list[0]是a的系數,list[1]是b的系數 list[0] * a + list[1] * b = 1,但是有可能得到的list[1]是負數
def ex_euclid(a, b, list):
# b==0時 a 為先求出最大公約數
????if b == 0:
????????list[0] = 1L
????????list[1] = 0L
????????list[2] = a
????else:
#把b作為a傳入函數,會形成一個交替的過程以 8,3為例,以次為[8,3],[3,2],[2,1],[1,0],即函數入棧時,第二個參數的值為入棧后第一個參數的值
#對應著出棧時,函數的第一個參數的值等于出棧后第二個參數
# [8, 3], [3, 2], [2, 1], [1, 0]對應的list取值為[-1,3,1][1,-1,1][0,1,1][1,0,1]
????????ex_euclid(b, a % b, list)
????????temp = list[0]
????????# a%b = a - (a/b)*b ,b*x1 + (a%b)*y1 = gcd
# gcd = b*x1 + (a-(a/b)*b)*y1 = b*x1 + a*y1–(a/b)*b*y1 = a*y1 + b*(x1 – a/b*y1)
#故出棧時,函數的第二個參數的系數等于出棧后第一個參數的系數,出棧后第二個參數b的系數=x1 – a/b*y1
????????list[0] = list[1]
#算法 1
????????list[1] = temp - a / b * list[1]
# 3 * x1 + 2 * y1 = 1,x1已知= 1,y1 = (1 - 3 * x1 )/2
#算法2,結果一致,使用時注釋temp = list[0]
????????# list[1] = (list[2] - a * list[0]) / b
#求模反元素
def mod_inverse(a, b):
????# x = list[0],y = list[1],q = list[2]
????list = [0L, 0L, 0L]
????if a < b:
????????temp = a;a = b;b = temp;
????ex_euclid(a, b, list)
#改進,將負的模反元素變為正的模反元素,根據公式 ed ≡ 1 (mod (s)),ed + s * k ≡ 1 (mod (s)),k為任意整數,令 k = m * e,即e的整數倍
# e(d + s * m)≡ 1 (mod (s)), abs(b) < s,所以只加一次即可
#此處只對list[1]進行修改
????if list[1] < 0:
????????list[1] = a + list[1]
????return list[1]
RSA實現流程
1.先創建一個包含有接近一萬小質數的數組,隨機獲得一個30-31位數的十進制數字num,判斷是否與數組元素都互質,若不互質則+2,直到獲得一個都互質的整數
2.對num進行Miller-Rabin素性檢驗8次或者更多次。如果num沒有通過檢驗,重新隨機生成大整數重復之前步驟,否則認為num是素數。Miller-Rabin素性檢驗有一定概率會失敗。