RSA 是一個著名的非對稱式加密系統,其安全性建立在大數分解的難度上
目前 RSA 僅能透過旁通道攻擊(Side-Channel Attack)
接下來我們需要先介紹 RSA 所需的數學知識
我們會以
定義
在這個定義之下,如果說
這也就是費馬小定理
在 RSA 當中,我們會取兩個質數
此時的
一個整數
或是表示成
就如同要找到一個數
此時的
在 RSA 當中,我們會去找某個數字
由於是在模底下運作,這個是不成立的!
這裡還有一個重要的性質
因為唯有這個條件成立,才會存在模反元素
如果不知道為什麼,不妨舉幾個數字來試試看,例如
會發現到無論如何都不會找到解,這裡先不寫證明
在 python 當中要求模反元素,可以使用 python 的 modules 輕鬆達成
>>> from gmpy2 import invert
>>> invert(3, 7)
mpz(5)
>>> from Crypto.Util.number import inverse
>>> inverse(3, 7)
5
在 RSA 當中有幾個常用的符號
符號 | 意義 |
---|---|
明文 | |
密文 | |
加密指數 | |
解密指數 | |
模數 |
RSA 加密的流程如下:
RSA 解密流程如下
第一步的分解可以透過下列的工具協助
證明
已知
要證明
根據費馬小定理可知
因此
要做 RSA encryption 運算必須要是一串數字
做完 RSA decryption 後會得到的是一串數字
因此我們須要有可以將字串跟數字之間做轉換的工具,以下使用 pyCryptodome
>>> from Crypto.Util.number import long_to_bytes
>>> long_to_bytes(1567694910997846802713134265455563233695200125)
b'FLAG{long_to_bytes}'
>>> from Crypto.Util.number import bytes_to_long
>>> bytes_to_long(b'FLAG{bytes_to_long}')
1567694910997644772753309158996360910989059965
n = 641038552669922004650640356715638051098436533497923620822471
e = 65537
c = 537756663490573782815200170116248828935623440674192611059116
透過 RSA decryption 的公式
先求
因為這題的
則
#!/usr/bin/python3
from Crypto.Util.number import *
n = 641038552669922004650640356715638051098436533497923620822471
e = 65537
c = 537756663490573782815200170116248828935623440674192611059116
p = 670839939121655241517135431571
q = 955576010440355093873649387901
phi = (q - 1) * (p - 1)
d = inverse(e , phi)
print(long_to_bytes(pow(c , d , n)))
當
先看一下 RSA encryption
如果
也就是說
n = 5489682192316422625434821656898117285064026279334711820823698107520079986854633408581065889926979914843916643565952889381426707291132831900665471049142135375628581051708437366655536056332623163988398065066438737021804605433412757434719710964824708642384163317493038636229777958794272501125366402189914988842955471879476018505743333
e = 5
c = 8695825648499005816846784896009806853240301482143432878264036921549622676046441329661491555770611113772567007417487958975920139817671636465013430373715241659378827911749003801125730213937874981663934259422055079597638258783093392939657595833683452469465273839501
可以使用 Python 開多次方根的工具
>>> from gmpy2 import iroot
>>> iroot(27, 3)
(mpz(3), True)
輸出的結果第一個值會告訴我們結果,第二個值會告訴我們是否可以完全開
如果我們只要取數字的話
>>> from gmpy2 import iroot
>>> iroot(27, 3)[0]
mpz(3)
#!/usr/bin/python3
from gmpy2 import *
from Crypto.Util.number import *
n = 5489682192316422625434821656898117285064026279334711820823698107520079986854633408581065889926979914843916643565952889381426707291132831900665471049142135375628581051708437366655536056332623163988398065066438737021804605433412757434719710964824708642384163317493038636229777958794272501125366402189914988842955471879476018505743333
e = 5
c = 8695825648499005816846784896009806853240301482143432878264036921549622676046441329661491555770611113772567007417487958975920139817671636465013430373715241659378827911749003801125730213937874981663934259422055079597638258783093392939657595833683452469465273839501
flag = iroot(c , 5)[0]
print(iroot(c , 5))
print(long_to_bytes(flag).decode())
當
已知
其中
那麼
可以藉此得到
而
因此,在不分解
n1 = 3593874939884522738360081188589590942537982121904663270426462920730452822866381549106050173717138442489161338601666460445680189833995562392260726330939659090382885163937026571902869123413995198480029534611435826740336455815556935256575605610959114838074076799488081282449
n2 = 3593874939884522738360081188589590942537982121904663270426462920730452822866381549106050173717138442489161338601666460449137123359408777399863055172836432363160566421893035214503585639512339952271274564675355484330533094284391934282566872891805164209109744997389364339953
e = 65537
c2 = 1452263324646172405378479278086175738198155482064889627046654734720235493221282450178456894139596530399913842093175193746761395688114601838969755236143605806012997830096042176122993514497200279356143322897735756185292885577845775294136832076476786425175672990487855382256
根據前面的敘述,可以分得到
有了
#!/usr/bin/python
from Crypto.Util.number import long_to_bytes , inverse
n1 = 3593874939884522738360081188589590942537982121904663270426462920730452822866381549106050173717138442489161338601666460445680189833995562392260726330939659090382885163937026571902869123413995198480029534611435826740336455815556935256575605610959114838074076799488081282449
n2 = 3593874939884522738360081188589590942537982121904663270426462920730452822866381549106050173717138442489161338601666460449137123359408777399863055172836432363160566421893035214503585639512339952271274564675355484330533094284391934282566872891805164209109744997389364339953
e = 65537
c2 = 1452263324646172405378479278086175738198155482064889627046654734720235493221282450178456894139596530399913842093175193746761395688114601838969755236143605806012997830096042176122993514497200279356143322897735756185292885577845775294136832076476786425175672990487855382256
pq = n1
p_q = (n2 - n1 - 4) // 2
phi1 = pq - p_q + 1
phi2 = pq + p_q + 1
d1 = inverse(e , phi1)
d2 = inverse(e , phi2)
c1 = pow(c2 , d2 , n2)
flag = pow(c1 , d1 , n1)
print(long_to_bytes(flag))
Wiener Attack 是在特定情況下提供我們猜測
透過猜測
接下來我們需要先介紹 Wiener Attack 所需的數學知識
在數學當中連分數以下列方式表達:
並且也可以用 list 的方式表達,我們會稱呼為 continued fraction expansion
並且可以保證所有的有理數都有有限的連分數展開式
以
從範例當中可以發現,我們會不斷的透過倒數以及商數使得連分數不斷擴張,直到商數為
# Continued Fraction Expansion of Rationals
def cf_expansion(n, d):
e = []
q = n // d
r = n % d
e.append(q)
while r != 0:
n, d = d, r
q = n // d
r = n % d
e.append(q)
return e
同時也會有一個數列
也就是一個不斷逼近於
同樣的,對於一個小數也可以寫成連分數的型態
以
def convergents(e):
n = [] # Nominators
d = [] # Denominators
for i in range(len(e)):
if i == 0:
ni = e[i]
di = 1
elif i == 1:
ni = e[i]*e[i-1] + 1
di = e[i]
else: # i > 1
ni = e[i]*n[i-1] + n[i-2]
di = e[i]*d[i-1] + d[i-2]
n.append(ni)
d.append(di)
yield (ni, di)
接下來是關於 Wiener Attack 的觀察
接下來同乘以
從觀察一可以發現,只要有
我們知道
所以
從觀察二可以發現,只要知道
從上面的兩個觀察當中可以得知,只要解出
而 Wiener Attack 其實就是提供我們一種猜
回顧觀察二並將式子同除以
由於
整理式子後會發現
而
得到這個結論後,我們就可以透過尋找
每猜一次就帶回去求
知道
知道
而計算 continued fraction expansion 實際上就是在做輾轉相除法
所以嘗試所有的 convergents 需要
並且
總時間複雜度為
#!/usr/bin/python3
from random import SystemRandom
from gmpy2 import isqrt, c_div , invert
from Crypto.Util.number import getPrime , bytes_to_long
urandom = SystemRandom()
def create_keypair(size):
while True:
p = getPrime(size // 2)
q = getPrime(size // 2)
if q < p < 2 * q:
break
n = p * q
phi = (p - 1) * (q - 1)
max_d = c_div(isqrt(isqrt(n)) , 3)
max_d_bits = max_d.bit_length() - 1
while True:
d = urandom.getrandbits(max_d_bits)
try:
e = int(invert(d, phi))
if (e * d) % phi == 1:
break
except:
continue
return n , e , d , p , q
n , e , d , p , q = create_keypair(1024)
from secret import flag
m = bytes_to_long(flag)
c = pow(m , e , n)
a = """n = {}
e = {}
c = {}""".format(n , e , c)
with open("output.py" , "w") as f:
f.write(a)
n = 68126437388977012491646508485989748986441131328587518477687317430610358060299259889270096202942172667026765693121007244799277906816721129879871839668534245685223273320285357819059314305041568152938631311930694380458197864770594205779250872221180313165818499006934672425382344026926216633834392646021142886769
e = 15950795120525802915045576886792784828953195706868419082039089065630551280007920240748167126298220976040006667069839541710102987502293373955109113066554922904845606595280968695858510227233594594345712943776826231898942926115077969092158946886802684295287362659572633661590452939636520740933192957991470727487
c = 64084228016799140236089362630474537548271913453940735254127977713219774643382144415063040580056793784702386932065587548669067894646547290107939535980687043376839097247039277684116912998816751608587498829246494557174907620772811870282348713591834440433602622206371153329743437983172303794473772691521200966081
根據前面的推論,先將 convergents of the continued fraction expasion 算出來
接著對於每一組 convergents 去猜測
最後求出
#!/usr/bin/python3
from Crypto.Util.number import long_to_bytes
from gmpy2 import iroot
n = 68126437388977012491646508485989748986441131328587518477687317430610358060299259889270096202942172667026765693121007244799277906816721129879871839668534245685223273320285357819059314305041568152938631311930694380458197864770594205779250872221180313165818499006934672425382344026926216633834392646021142886769
e = 15950795120525802915045576886792784828953195706868419082039089065630551280007920240748167126298220976040006667069839541710102987502293373955109113066554922904845606595280968695858510227233594594345712943776826231898942926115077969092158946886802684295287362659572633661590452939636520740933192957991470727487
c = 64084228016799140236089362630474537548271913453940735254127977713219774643382144415063040580056793784702386932065587548669067894646547290107939535980687043376839097247039277684116912998816751608587498829246494557174907620772811870282348713591834440433602622206371153329743437983172303794473772691521200966081
def cf_expansion(n, d):
e = []
q = n // d
r = n % d
e.append(q)
while r != 0:
n, d = d, r
q = n // d
r = n % d
e.append(q)
return e
def convergents(e):
n = [] # Nominators
d = [] # Denominators
for i in range(len(e)):
if i == 0:
ni = e[i]
di = 1
elif i == 1:
ni = e[i]*e[i-1] + 1
di = e[i]
else: # i > 1
ni = e[i]*n[i-1] + n[i-2]
di = e[i]*d[i-1] + d[i-2]
n.append(ni)
d.append(di)
yield (ni, di)
def solve(phi, n):
b = (phi - n - 1)
D = iroot(pow(phi - n - 1, 2)-4*n, 2)[0]
return ((-b + D) // 2, (-b - D) // 2)
exp_lst = cf_expansion(e, n)
for i in convergents(exp_lst):
k, d = i
if(k == 0):
continue
phi = e*d//k
p, q = solve(phi, n)
if(p*q == n):
print(long_to_bytes(pow(c, d, n)))
break
這邊我們只詳細介紹了 little e 以及 twin prime ,不過還有其他有趣的部份可以繼續研究
Crypto