CTF密碼學中RSA學習以及總結
關於CTF中RSA學習筆記
RSA簡介
為了方便理解,先對RSA金鑰體制做個簡略的介紹。
- 選擇兩個大的引數,計算出模數 N = p * q
- 計算尤拉函式 φ = (p-1) * (q-1),然後選擇一個e(1<e<φ),並且e和φ互質(互質:公約數只有1的兩個整數)
- 取e的模反數d,計算方法為:e * d ≡ 1 (mod φ) (模反元素:如果兩個正整數e和n互質,那麼一定可以找到整數d,使得 e * d - 1 被n整除,或者說e * d被n除的餘數是1。這時,d就叫做e的“模反元素”。尤拉定理可以用來證明模反元素必然存在。兩個整數a,b,它們除以整數M所得的餘數相等:a ≡ b(mod m),比如說5除3餘數為2,11除3餘數也為2,於是可寫成11 ≡ 5(mod 3)。)
- 對明文m進行加密:c = pow(m, e, N),可以得到密文c。
- 對密文c進行解密:m = pow(c, d, N),可以得到明文m。
整理:
- p 和 q:兩個大的質數,是另一個引數
N
的的兩個因子。 - N:大整數,可以稱之為
模數
- e 和 d:互為無反數的兩個指數
- c 和 m:密文和明文
- (N, e):公鑰
- (N, d):私鑰
- pow(x, y, z):效果等效
pow(x, y)1 % z
, 先計算x的y次方,如果存在另一個引數z
,需要再對結果進行取模。 - 金鑰長度:n以二進位制表示的的位數,例如金鑰長度為512代表n用二進位制表示的長度為512bit。1
RSA安全性分析
對於RSA加密演算法,公鑰(N, e)
N
,然後計算尤拉函式φ(n)=(p-1) * (q-1)
,再通過d * e ≡ 1 mod φ(N)
,即可計算出 d
,然後就可以使用私鑰(N, d)
通過m = pow(c,d,N)
解密明文。
保障RSA的安全性
1.定期更換金鑰 2.不同的使用者不可以使用相同的模數N 3.p與q的差值要大,最好是差幾個位元 4.p-1與q-1都應該有大的素因子,一般建議選擇的兩個大素數p、q使得p=2p+1和q=2q+1也是素數 5.e的選擇不要太小 6.d的選擇也是不可以太小,最好滿足
d>=n的4分之1
常用的攻擊方法
直接分解模數N
直接分解模數N
是最直接的攻擊方法,也是最困難的方法。具體的解析同上RSA安全性分析
CTF原題
{920139713,19}
704796792
752211152
274704164
18414022
368270835
483295235
263072905
459788476
483295235
459788476
663551792
475206804
459788476
428313374
475206804
459788476
425392137
704796792
458265677
341524652
483295235
534149509
425392137
428313374
425392137
341524652
458265677
263072905
483295235
828509797
341524652
425392137
475206804
428313374
483295235
475206804
459788476
306220148
分解N
可以通過線上網站http://www.factordb.com/index.php
分解920139713
可以得到p
和q
的值為18443
和49891
,現在已知p
、q
、e
以及c
,可以通過前三個引數求出d
安裝gmpy2
可以參考https://blog.csdn.net/wanzt123/article/details/71036184
import gmpy2
p = gmpy2.mpz(18443)#初始化大整數
q = gmpy2.mpz(49891)
e = gmpy2.mpz(19)
phi_n = (p-1)*(q-1)
d = gmpy2.invert(e,phi_n)#invert(x,m)返回y使得x * y == 1 modulo m,如果不存在y,則返回0
print("p=%s,q=%s,e=%s"%(p,q,e))
print("d is:\n%s"%d)
p=18443,q=49891,e=19
d is:
96849619
到目前為止,已經求出p,q,e,d,n,c,然後可以求出相應的明文M,
#求明文
import gmpy2
n = 920139713
d = 96849619
c = """
704796792
752211152
274704164
18414022
368270835
483295235
263072905
459788476
483295235
459788476
663551792
475206804
459788476
428313374
475206804
459788476
425392137
704796792
458265677
341524652
483295235
534149509
425392137
428313374
425392137
341524652
458265677
263072905
483295235
828509797
341524652
425392137
475206804
428313374
483295235
475206804
459788476
306220148
"""
result = ""
c_list = c.split()
#print(c_list)
for i in c_list:
result += chr(pow(int(i),d,n))
print(result)
對RSA的公共模數攻擊
適用於:使用相同的模數 N 、不同的私鑰,加密同一明文訊息
基本原理
假如採用兩個或者兩個以上的公鑰(N,e)來加密同一條資訊,可以得到下面的結論:
c1 = pow(m, e1, N) c2 = pow(m, e2, N)
分別拿對應的私鑰來加密,可以得到相同的明文m
m = pow(c1, d1, N) m = pow(c2, d2, N)
假設攻擊者已知n,e1,e2,c1,c2,即可可以得到明文m,因為e1和e2互質,所以使用歐幾里得演算法
(用於計算兩個整數a,b的最大公約數)可以找到能夠滿足以下條件的x
,y
:
pow(x,e1)+pow(y,e2)=1
假設x為負數,需再使用歐幾里得演算法來計算
pow(c1,-1)
則可以得到
pow(pow(c1,-1),-x) * pow(c2,y) = p mod(n)
# coding=utf-8
import gmpy2
def ByteToHex(bins):
return ''.join(["%02X" % x for x in bins]).strip()
def n2s(num):
t = hex(num)[2:]
if len(t) % 2 == 1:
t = '0' + t
return ''.join([chr(int(b, 16)) for b in [t[i:i + 2] for i in range(0, len(t), 2)]])
n = 0x00b0bee5e3e9e5a7e8d00b493355c618fc8c7d7d03b82e409951c182f398dee3104580e7ba70d383ae5311475656e8a964d380cb157f48c951adfa65db0b122ca40e42fa709189b719a4f0d746e2f6069baf11cebd650f14b93c977352fd13b1eea6d6e1da775502abff89d3a8b3615fd0db49b88a976bc20568489284e181f6f11e270891c8ef80017bad238e363039a458470f1749101bc29949d3a4f4038d463938851579c7525a69984f15b5667f34209b70eb261136947fa123e549dfff00601883afd936fe411e006e4e93d1a00b0fea541bbfc8c5186cb6220503a94b2413110d640c77ea54ba3220fc8f4cc6ce77151e29b3e06578c478bd1bebe04589ef9a197f6f806db8b3ecd826cad24f5324ccdec6e8fead2c2150068602c8dcdc59402ccac9424b790048ccdd9327068095efa010b7f196c74ba8c37b128f9e1411751633f78b7b9e56f71f77a1b4daad3fc54b5e7ef935d9a72fb176759765522b4bbc02e314d5c06b64d5054b7b096c601236e6ccf45b5e611c805d335dbab0c35d226cc208d8ce4736ba39a0354426fae006c7fe52d5267dcfb9c3884f51fddfdf4a9794bcfe0e1557113749e6c8ef421dba263aff68739ce00ed80fd0022ef92d3488f76deb62bdef7bea6026f22a1d25aa2a92d124414a8021fe0c174b9803e6bb5fad75e186a946a17280770f1243f4387446ccceb2222a965cc30b3929
e1 = 17
e2 = 65537
# gmpy2.gcdext(e1,e2)的執行結果為元組(mpz(1), mpz(30841), mpz(-8)),所以元組的第0個值不能取,第1個值才是s1,第2個值由於是負數,所以要取反,變為正數
# gcdext(a,b)返回一個3元素元組(g,s,t)
# g == gcd(a,b)最大公約數和g == a * s + b * t
s = gmpy2.gcdext(e1, e2)
s1 = s[1]
s2 = -s[2]
file1 = open("veryhardRSA/flag.enc1", 'rb').read() # 這裡的路徑要自己修改
c1 = int(ByteToHex(file1), 16)
file2 = open("veryhardRSA/flag.enc2", 'rb').read() # 這裡的路徑要自己修改
c2 = int(ByteToHex(file2), 16)
# 由於根據前面的運算,s1是正數,s2是負數,後面需要運算c2的s2次冪。因為在數論模運算中,要求一個數的負數次冪,與常規方法並不一樣,比如此處要求c2的s2次冪,就要先計算c2的模反元素c2r,然後求c2r的-s2次冪。
c2 = gmpy2.invert(c2, n)
m = (pow(c1, s1, n) * pow(c2, s2, n)) % n
print(n2s(m))
RSA小指數e攻擊
如果RSA系統的公鑰e選取較小的值,可以使得加密和驗證簽名的速度有所提高,但是如果e的選取太小,就容易受到攻擊。
有三個分別使用不同的模數n1,n2,n3,但是都選取e=3,加密同一個明文可以得到:
c1 = pow(m,3,n1) c2 = pow(m,3,n2) c3 = pow(m,3,n3)
一般情況下,n1,n2,n3互素,否則會比較容易求出公因子,從而安全性大幅度的減低。
RSA選擇密文攻擊
在此種攻擊模型中,攻擊者需要掌握的內容包括:加密演算法、截獲的部分密文、自己選擇的密文訊息以及相應的被解密的明文。
CTF中常見的題型分析
可能使用到的工具
常見的題型
已知p、q、e求解d
思路
根據尤拉函式
,可以通過p、q計算出尤拉函式值
φ(n) = (p-1) * (q-1)
之後再根據以下的公式反推出d
d * e ≡ 1 mod φ(N)
在一次RSA金鑰對生成中,假設p=473398607161,q=4511491,e=17 求解出d 將得到的d提交 根據思路可以python進行實現:
# 已知p、q、e求解d
import gmpy2
p = gmpy2.mpz(473398607161)
q = gmpy2.mpz(4511491)
e = gmpy2.mpz(17)
n = (p-1)*(q-1)
d = gmpy2.invert(e,n)
print("d is:\n%s"%d)
已知p、q、e、c求解明文
思路
根據常規的思路,求解出明文m
,必須通過通過以下的公式
m ≡ pow(c,d) mod n
現在缺少的引數有d
、n
,其中的n
可以通過以下的公式可以求出
n = p * q
而d
可以通過以下公式求出
p = 9648423029010515676590551740010426534945737639235739800643989352039852507298491399561035009163427050370107570733633350911691280297777160200625281665378483 q = 11874843837980297032092405848653656852760910154543380907650040190704283358909208578251063047732443992230647903887510065547947313543299303261986053486569407 e = 65537 c = 83208298995174604174773590298203639360540024871256126892889661345742403314929861939100492666605647316646576486526217457006376842280869728581726746401583705899941768214138742259689334840735633553053887641847651173776251820293087212885670180367406807406765923638973161375817392737747832762751690104423869019034 Use RSA to find the secret message
根據上面的思路可以實現以下的python指令碼
import gmpy2
p = 9648423029010515676590551740010426534945737639235739800643989352039852507298491399561035009163427050370107570733633350911691280297777160200625281665378483
q = 11874843837980297032092405848653656852760910154543380907650040190704283358909208578251063047732443992230647903887510065547947313543299303261986053486569407
e = 65537
c = 83208298995174604174773590298203639360540024871256126892889661345742403314929861939100492666605647316646576486526217457006376842280869728581726746401583705899941768214138742259689334840735633553053887641847651173776251820293087212885670180367406807406765923638973161375817392737747832762751690104423869019034
# 1.已知的p和q求出n
n = p * q
# 2.根據已知的條件求出d
phi_n = (p-1)*(q-1)
d = gmpy2.invert(e,phi_n)
#print(d)
#求出明文
m = pow(c,d,n)
print("m=\n%s"%m)
已知c、n、e求解明文
思路 先根據
n = p * q
對已知的n進行大數分解得到p、q,一般通過線上或者自己通過指令碼實現
根據尤拉函式
,可以通過p、q計算出尤拉函式值
φ(n) = (p-1) * (q-1)
之後再根據以下的公式反推出d
d * e ≡ 1 mod φ(N)
最後對密文c進行解密:m = pow(c, d, N),可以得到明文m。
例題
N=0xee290c7a603fc23300eb3f0e5868d056b7deb1af33b5112a6da1edc9612c5eeb4ab07d838a3b4397d8e6b6844065d98543a977ed40ccd8f57ac5bc2daee2dec301aac508f9befc27fae4a2665e82f13b1ddd17d3a0c85740bed8d53eeda665a5fc1bed35fbbcedd4279d04aa747ac1f996f724b14f0228366aeae34305152e1f430221f9594497686c9f49021d833144962c2a53dbb47bdbfd19785ad8da6e7b59be24d34ed201384d3b0f34267df4ba8b53f0f4481f9bd2e26c4a3e95cd1a47f806a1f16b86a9fc5e8a0756898f63f5c9144f51b401ba0dd5ad58fb0e97ebac9a41dc3fb4a378707f7210e64c131bca19bd54e39bbfa0d7a0e7c89d955b1c9f e=0x10001 c=0x3dbf00a02f924a70f44bdd69e73c46241e9f036bfa49a0c92659d8eb0fe47e42068eaf156a9b3ee81651bc0576a91ffed48610c158dc8d2fb1719c7242704f0d965f8798304925a322c121904b91e5fc5eb3dc960b03eb8635be53b995217d4c317126e0ec6e9a9acfd5d915265634a22a612de962cfaa2e0443b78bdf841ff901423ef765e3d98b38bcce114fede1f13e223b9bd8155e913c8670d8b85b1f3bcb99353053cdb4aef1bf16fa74fd81e42325209c0953a694636c0ce0a19949f343dc229b2b7d80c3c43ebe80e89cbe3a3f7c867fd7cee06943886b0718a4a3584c9d9f9a66c9de29fda7cfee30ad3db061981855555eeac01940b1924eb4c301
先將n
轉為10進位制,可以通過以下的指令碼
#16轉10進位制,n
n = int(0xee290c7a603fc23300eb3f0e5868d056b7deb1af33b5112a6da1edc9612c5eeb4ab07d838a3b4397d8e6b6844065d98543a977ed40ccd8f57ac5bc2daee2dec301aac508f9befc27fae4a2665e82f13b1ddd17d3a0c85740bed8d53eeda665a5fc1bed35fbbcedd4279d04aa747ac1f996f724b14f0228366aeae34305152e1f430221f9594497686c9f49021d833144962c2a53dbb47bdbfd19785ad8da6e7b59be24d34ed201384d3b0f34267df4ba8b53f0f4481f9bd2e26c4a3e95cd1a47f806a1f16b86a9fc5e8a0756898f63f5c9144f51b401ba0dd5ad58fb0e97ebac9a41dc3fb4a378707f7210e64c131bca19bd54e39bbfa0d7a0e7c89d955b1c9f)
print("n=\n%s"%n)
然後線上分解出p=57970027和q=518629368090170828331048663550229634444384299751272939077168648935075604180676006392464524953128293842996441022771890719731811852948684950388211907532651941639114462313594608747413310447500790775078081191686616804987790818396104388332734677935684723647108960882771460341293023764117182393730838418468480006985768382115446225422781116531906323045161803441960506496275763429558238732127362521949515590606221409745127192859630468854653290302491063292735496286233738504010613373838035073995140744724948933839238851600638652315655508861728439180988253324943039367876070687033249730660337593825389358874152757864093,線上地址http://www.factordb.com/index.php
#coding=utf-8
import gmpy2
from binascii import a2b_hex
#n的16進位制轉10進位制
n = int(0xee290c7a603fc23300eb3f0e5868d056b7deb1af33b5112a6da1edc9612c5eeb4ab07d838a3b
#print("n=\n%s"%n)
#通過線上分解出的p和q,以及給定的e,求出d
e = gmpy2.mpz(int(0x10001))
p = 57970027
q = 5186293680901708283310486635502296344443842997512729390771686489350756041806760063
phi_n = (q-1)*(p-1)
d = hex(gmpy2.invert(e,phi_n))
#print("d的十六進位制為:\n%s"%d)
#通過n,d,e,c求解明文
d = 0x9186c78d098af6815622ea9901cf84a89ead578a6dbdded7d7fc63531756239dc586501216fc2e4b
e = 0x10001
c = 0x3dbf00a02f924a70f44bdd69e73c46241e9f036bfa49a0c92659d8eb0fe47e42068eaf156a9b3ee8
n = 0xee290c7a603fc23300eb3f0e5868d056b7deb1af33b5112a6da1edc9612c5eeb4ab07d838a3b4397
#flag = hex(pow(c,d,n))[1:].decoding("hex")
flag = a2b_hex(hex(pow(c,d,n))[2:])
print(flag)
已知c、e,求解明文
思路 一般情況下,求解明文的公式為
m = pow(c, d, N)
在僅已知c
、e
的情況下,幾乎不能反推出其他的引數,python中提供一個Crypto
的庫,通過呼叫相關的函式模組,可以實現對n
、e
的求解,之後再通過分解大數n
等方法,求出其他的引數。
- 由於
pycrypto
已經停止更新,推薦使用pycryptodome
安裝命令sudo pip3 install pycryptodome
首先通過公鑰檔案public.pem
獲取n
、e
# 通過公鑰檔案獲取n、e
from Crypto.PublicKey import RSA
public = RSA.importKey(open("./RSA/public.pem").read())
n = public.n
e = public.e
print("n=\n%s\ne=\n%s"%(n,e))
n=
74207624142945242263057035287110983967646020057307828709587969646701361764263
e=
65537
通過線上網站
http://www.factordb.com/index.php? 分解n
可以得到p=258631601377848992211685134376492365269
以及q=286924040788547268861394901519826758027
生成私鑰
#python2
from Crypto.PublicKey import RSA
keypair = RSA.generate(1024)
keypair.p = 258631601377848992211685134376492365269
keypair.q = 286924040788547268861394901519826758027
keypair.e = 65537
keypair.n = keypair.p * keypair.q
phi_n = (keypair.p-1) * (keypair.q-1)
i = 1
while (True):
x = (phi_n * i ) + 1
if (x % keypair.e == 0):
keypair.d = x / keypair.e
break
i += 1
private = open('private.pem','w')
private.write(keypair.exportKey())
private.close()
最後使用生成的私鑰將加密檔案解密
openssl rsautl -decrypt -in ./RSA/flag.enc -inkey private.pem -out flag.txt
後記
這個花了一個下午來整理的,部分參考其他網友的文章,暫且放在原創
類中。網安
和CTF
這條路仍然需要慢慢學習。有興趣的可以聯絡我一起學習交流。這篇筆記
會不定時的更新。