1. 程式人生 > >CTF密碼學中RSA學習以及總結

CTF密碼學中RSA學習以及總結

關於CTF中RSA學習筆記

RSA簡介

為了方便理解,先對RSA金鑰體制做個簡略的介紹。

  1. 選擇兩個大的引數,計算出模數 N = p * q
  2. 計算尤拉函式 φ = (p-1) * (q-1),然後選擇一個e(1<e<φ),並且e和φ互質(互質:公約數只有1的兩個整數)
  3. 取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)。)
  4. 對明文m進行加密:c = pow(m, e, N),可以得到密文c。
  5. 對密文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)

為公鑰,可以任意公開,破解RSA最直接(亦或是暴力)的方法就是分解整數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

分解可以通過線上網站http://www.factordb.com/index.php 分解920139713可以得到pq的值為1844349891,現在已知pqe以及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的最大公約數)可以找到能夠滿足以下條件的xy

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

現在缺少的引數有dn,其中的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)

在僅已知ce的情況下,幾乎不能反推出其他的引數,python中提供一個Crypto的庫,通過呼叫相關的函式模組,可以實現對ne的求解,之後再通過分解大數n等方法,求出其他的引數。

  • 由於pycrypto已經停止更新,推薦使用pycryptodome安裝命令 sudo pip3 install pycryptodome

首先通過公鑰檔案public.pem獲取ne

# 通過公鑰檔案獲取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這條路仍然需要慢慢學習。有興趣的可以聯絡我一起學習交流。這篇筆記會不定時的更新。