1. 程式人生 > 實用技巧 >CTF中的RSA 演算法

CTF中的RSA 演算法

1.質數(素數)是指在大於1的自然數中,除了1和它本身以外不再有其他因數的自然數。
2.合數是指比1大但不是素數的數
3.約數(因數)整數a除以整數b(b≠0) 除得的商正好是整數而沒有餘數,我們就說a能被b整除,或b能整除a。a稱為 b的倍數,b稱為a的約數
4.互質數:如果兩個整數a,b的最大公因數(greatest common divisor)為1,即gcb(a,b)=1,那麼稱a,b兩數互質
5.尤拉函式是指設m為正整數,則1,2,3,4.......,m中與m互素的整數的個數記為φ(m),叫做尤拉函


RSA加解密涉及變數

N(n):模數(modulus)

p 和 q :N的兩個因子(factor)

e 和 d:(金鑰) 互為模反數的兩個指數(exponent)

c 和 m:分別是密文和明文,這裡一般指的是一個十進位制的數還有一個就是n的尤拉函式值

尤拉函式值:r

pow(x, y, z):效果等效pow(x, y)1 % z, 先計算x的y次方,如果存在另一個引數z,需要再對結果進行取模。

RSA 金鑰流程

1.選擇兩個大的引數,計算出模數 N = p * q

2.計算尤拉函式 φ = (p-1) * (q-1),然後選擇一個e(1<e<φ),並且e和φ互質(互質:公約數只有1的兩個整數)

3.選一個整數e,滿足條件1<e<φ(m),且gcd(φ(m),e)=1。

4.取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)。)

5.對明文m進行加密:c = pow(m, e, N),可以得到密文c。

6.對密文c進行解密:m = pow(c, d, N),可以得到明文m。

7.以{e,n}為公開金鑰,{d,n}為祕密金鑰。

對於RSA加密演算法,公鑰{e,n}為公鑰,可以任意公開,破解RSA最直接(亦或是暴力)的方法就是分解整數N,然後計算尤拉函式φ(n)=(p-1) * (q-1),再通過d * e ≡ 1 mod φ(N),即可計算出 d,然後就可以使用私鑰{d,n}通過m = pow(c,d,N)解密明文。

常見攻擊方法

已知pqe或者已知ne求出d

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(e,r)返回d使得e * d == 1 mod r,如果不存在d,則返回0
print("p={0},q={1},e={2}".format(p,q,e))
print("d is:\n%s"%d)

注:gmpy2:開源的高精度算數運算庫https://blog.csdn.net/x_yhy/article/details/83903367

​ 分解得到p q可以通過線上網站http://www.factordb.com/index.php

已經求出dnc,然後可以求出相應的明文m

#求明文
import gmpy2
n = 920139713   #模數
d = 96849619    #金鑰
c = """
704796792
752211152
274704164
...  #密文
"""
result = ""
c_list = c.split()
#print(c_list)
for i in c_list:
    result += chr(pow(int(i),d,n))
print(result)

已知cen求m

結合以上兩種方法,在知道n的前提下可求·pq,利用pqe可以求出d,,從而因為已知dnc,求出相應的明文m


利用n的公約數

當題目給出若干個模數n1,n2......,且模數很大。如果兩次加密的n1n2具有相同的素因子,那麼我們可以利用歐幾里德演算法直接分解n1n2.從而計算出兩個n的最大公約數p

素因子的定義:對於一個數n來說,將它的因子拆到若干個素數相乘,這些素數被稱為n的素因子。
比如 12可以被拆為2 6
6不是質數,可以繼續拆為2*3
所以最後12的素因子就是 2, 3(不計重複元素)

識別此類題目,通常會發現題目給了若干個n,均不相同,並且都是2048bit,4096bit級別,無法直接分解http://www.factordb.com/index.php,並且明文都沒什麼聯絡,e也一般取65537。

#-*-coding:utf-8-*-
'''
求兩個數的最大公約數
演算法參考:https://zhidao.baidu.com/question/36550887.html
by:reborn
'''
import gmpy2
n1=
n2=
def gys1(n1,n2):    #輾轉相除法(歐幾里德演算法)
    if n1<n2:
        n1,n2=n2,n1
    while n2!=0:
        temp=n1%n2
        n1=n2
        n2=temp
    return n1
def gys2(n1,n2):    #更相減損法
    while n1!=n2:
        if n1<n2:
            n1,n2=n2,n1
        temp=n1-n2
        n1=temp
    return n1
p=gys2(n1,n2)
print ("p=",p)

#求q1,q2

q1=n1//p
q2=n2//p
print("q1=",q1)
print("q2=",q2)

#求d_1,d_2

p0 = gmpy2.mpz(p)#初始化大整數
q_1 = gmpy2.mpz(q1)
q_2 = gmpy2.mpz(q2)
e = gmpy2.mpz(65537)
r_1 = (p0-1)*(q_1-1)
r_2 = (p0-1)*(q_2-1)
d_1 = gmpy2.invert(e,r_1)  # invert(e,r)返回d使得e * d == 1 mod r,如果不存在d,則返回0
d_2 = gmpy2.invert(e,r_2)
print("d_1=",d_1)
print("d_2=",d_2)

# 求c1,c2

c1=
c2=
m1 = pow(c1, d_1, n1)
m2 = pow(c2, d_2, n2)
print("m1=",m1)
print("m2=",m2)

根據歐幾里德演算法算出的p之後,再用n除以p即可求出q,由此可以得到的引數有pqne,再使用常規方法計算出d,即可破解密文。

m = pow(c, d, N),可以得到明文m


共模攻擊

如果在RSA的使用中使用了相同的模n對相同的明文m進行了加密,那麼就可以在不分解n的情況下還原出明文m的值。

c1 = m^e1 mod n
c2 = m^e2 mod n

識別:非常簡單,若干次加密,每次n都一樣,明文根據題意也一樣即可。


已知私鑰檔案、c求m

題目中給出了私鑰檔案private.pem和flag.enc

pem檔案通常是包含了-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----,是 Base64 編碼的二進位制內容
使用私鑰解密密文的方式

使用openssl工具
利用如下命令:

rsautl -decrypt -in flag.enc(密文名稱) -inkey private.pem


已知公鑰檔案、c求m

題目中給出了public.pem和密文flag.enc

openssl rsa -pubin -text -modulus -in warmup -in pubkey.pem
​ [提取出pubkey.pem中的引數]

得到n,化為十進位制

將n分解為P,q

python rsatool.py -o private.pem -e 65537 -p 275127860351348928173285174381581152299 -q 319576316814478949870590164193048041239

[使用rsatool生成私鑰檔案: private.pem]

openssl rsautl -decrypt -in flag.enc -inkey private.pem


低加密指數攻擊

在RSA中e也稱為加密指數。由於e是可以隨意選取的,選取小一點的e可以縮短加密時間,但是選取不當的話,就會造成安全問題。

e=3時的小明文攻擊

當e=3時,如果明文過小,導致明文的三次方仍然小於n,那麼通過直接對密文三次開方,即可得到明文。

(1)m3<n,也就是說m3=c;
(2)m3>n,即(m3+kn)mod n=c(爆破k,不知道k取什麼值)。

  • 第一種情況 根據 c = pow(m, e, N) 可知:

當e=3時,如果明文過小,導致明文的三次方仍然小於n,那麼通過直接對密文三次開方,即可得到明文。

  • 第二種情況 如果明文的三次方比n大,但是不夠大,那麼設k,有: c=(m^3+kn)mod n

爆破k,如果(c-kn)能開三次根式,那麼可以直接得到明文。

識別:

推薦在e=3的時候首先嚐試這種方法。

openssl rsa -pubin -in pubkey.pem (讀取公鑰內容)
openssl rsa -pubin in pubkey.pem -text(以文字格式輸出公鑰內容),從這一步可以知道e的值

從而判斷為低加密指數攻擊


低加密指數廣播攻擊

低加密指數廣播攻擊,即如果選取的加密指數較低,並且使用了相同的加密指數給一個接收者傳送了相同的資訊(或者給一群接收者傳送了相同的資訊),那麼可以進行廣播攻擊得到明文。

假如我們需要將一份明文進行多份加密,但是每份使用不同的金鑰,金鑰中的模數n不同但指數e相同且很小,我們只要拿到多份密文和對應的n就可以利用中國剩餘定理進行解密。

適用

只要滿足以下情況,我們就可以考慮實用低加密指數廣播攻擊:

1.加密指數e非常小
2.一份明文使用不同的模數n,相同的加密指數e進行多次加密
3.可以拿到每一份加密後的密文和對應的模數n、加密指數e

低加密指數廣播攻擊指令碼

# coding:utf8

from struct import pack,unpack
import zlib
import gmpy
def my_parse_number(number):
    string = "%x" % number
    #if len(string) != 64:
    #    return ""
    erg = []
    while string != '':
        erg = erg + [chr(int(string[:2], 16))]
        string = string[2:]
    return ''.join(erg)
def extended_gcd(a, b):
    x,y = 0, 1
    lastx, lasty = 1, 0
    while b:
        a, (q, b) = b, divmod(a,b)
        x, lastx = lastx-q*x, x
        y, lasty = lasty-q*y, y
    return (lastx, lasty, a)
def chinese_remainder_theorem(items):
  N = 1
  for a, n in items:
    N *= n
  result = 0
  for a, n in items:
    m = N/n
    r, s, d = extended_gcd(n, m)
    if d != 1:
      N=N/n
      continue
      #raise "Input not pairwise co-prime"
    result += a*s*m
  return result % N, N
  //中國剩餘定理 , 輸入多組c和多組n,以及較小的指數e
sessions=[
{"c": , "e": , "n": },
{"c": , "e": , "n": },
{"c": , "e": , "n": }
]

data = []
for session in sessions:
    e=session['e']
    n=session['n']
    msg=session['c']
    data = data + [(msg, n)]
print "Please wait, performing CRT"
x, n = chinese_remainder_theorem(data)
e=session['e']
realnum = gmpy.mpz(x).root(e)[0].digits()
print my_parse_number(int(realnum))

n可以分解為多個素數

使用公鑰加密和使用私鑰解密流程(中國剩餘定理):
準備
首先,我們需要在在生成私鑰公鑰時,多生成幾個數:
我們的d是e對phi(n)的逆元,我們現在需要另外2個逆元(分別是對(p-1)和(q-1)的),既
1:計算dp,使得dp * e = 1 mod(p-1)
2:計算dq,使得dq * e = 1 mod(q-1)
此外需要第三個元素,既q對p的逆元
3:計算qInv,使得qInv * q = 1 mod p
​ 1 2 3 都作為私鑰的一部分。

dp = d mod p-1
dq = d mod q-1

計算:

使用公鑰加密:
若要加密明文m,則需要計算c = m^e mod n,c為密文。

使用私鑰解密:
1:m1=c^dp mod p
2:m2=c^dq mod q
3:h= (qInv((m1 - m2)mod p)) mod p
4:m = m2 + h
q
m就是明文。

例:n=17947
​ e=3
​ c=8363
​ m=???

import gmpy2
n=17947
p=137
q=131
e=3
c=8363
dp=gmpy2.invert(e,p-1)
dq=gmpy2.invert(e,q-1)
m1=pow(c,dp,p)
m2=pow(c,dq,q)
qInv=gmpy2.invert(q,p)
h=(qInv*((m1-m2)% p)) % p
m=m2+h*q
print(m)

多素數

例:n=p1p2p3=2279269
​ p1=137
​ p2=131
​ p3=127
​ e=19

預先計算:
dp = 19^-1 mod 137-1 = 43
dq = 19^-1 mod 131-1 = 89
dr = 19^-1 mod 127-1 = 73

若要解密密文 768924,則先計算
1:m1=768924^43 mod 137 = 102
2:m2=768924^89 mod 131 = 120
3:m3=768924^73 mod 127 = 5

等式1與等式2連列方程組計算:
qInv = 114
h = (qInv*((m1 - m2)mod p)) mod p = 3
m12 = m2 + h*q = 120 + 3*131 = 513

所以等式1與等式2的通用解為:513+k1*(131*137)
所以結合等式3問題可以變為:
m1=513  p=17947
m2=5    q=127
qInv*q≡ 1 mod p    ——>qInv=10316
h = (10316*((513 - 5)mod 17947)) mod 17947 =4
m = 5 + 4*127 = 513
......

jiaoben

import gmpy2
n=2279269
p1=137
p2=131
p3=127
e=19
c=768924
dp1=gmpy2.invert(e,p1-1)
dp2=gmpy2.invert(e,p2-1)
dp3=gmpy2.invert(e,p3-1)
m1=pow(c,dp1,p1)
m2=pow(c,dp2,p2)
m3=pow(c,dp3,p3)
qInv1=gmpy2.invert(p2,p1)
h1=(qInv1*((m1-m2) % p1)) % p1
m4=m2+h1*p2
p4=p1*p2
qInv2=gmpy2.invert(p3,p4)
h2=(qInv2*((m4-m3)% p4)) % p4
m=m3+h2*p3
print(m)

dp、dq洩露

dp = d mod p-1
dq = d mod q-1

這種引數是為了讓解密的時候更快速而產生的

已知p,q,dp,dq,c求m

import gmpy2
import binascii
import libnum
def decrypt(dp,dq,p,q,c):
    InvQ = gmpy2.invert(q,p)
    mp = pow(c,dp,p)
    mq = pow(c,dq,q)                   #求冪取模運算
    m=(((mp-mq)*InvQ)%p)*q+mq          #求明文公式
    print (binascii.unhexlify(hex(m)[2:]))
    print(libnum.n2s(m))
p = 
q = 
dp = 
dq = 
c = 
decrypt(dp,dq,p,q,c)

已知e,n,dp,c求m

import gmpy2
import libnum
import binascii
def getd(n,e,dp):
    for i in range(1,e):            #在範圍(1,e)之間進行遍歷
        if (dp*e-1)%i == 0:
            if n%(((dp*e-1)/i)+1)==0:    #存在p,使得n能被p整除
                p=((dp*e-1)/i)+1
                q=n/(((dp*e-1)/i)+1)
                phi = (p-1)*(q-1)         #尤拉定理
                d = gmpy2.invert(e,phi)%phi        #求模逆
                return d
e =
n = 
dp = 
c = 
d=getd(n,e,dp)
m=pow(c,d,n)                            #快速求冪取模運算
print(binascii.unhexlify(hex(m)[2:]))       #16進位制轉文字
print(libnum.n2s(m))

https://blog.csdn.net/qq_42939527/article/details/105202716


已知n,r求p,q

核心是通過n和r解出p和q

1.二分法,求得p,q

2.RSATool2v17中,輸入p,q,r,e,得到d  (指令碼也可)

3.通過m=pow(c,d,n)
注意:有時題目有要求,解出的可能是m乘上某一個引數,這是需要仔細審題

4.轉字元,得到flag

指令碼

import gmpy2
import numpy as np
np.set_printoptions(suppress=True)

n=gmpy2.mpz(14057332139537395701238463644827948204030576528558543283405966933509944444681257521108769303999679955371474546213196051386802936343092965202519504111238572269823072199...)
r=gmpy2.mpz(14057332139537395701238463644827948204030576528558543283405966933509944444681257521108769303999679955371474546213196051386802936343092965202519504111238572269823072199...)

c1=n-r+1
print (c1)

l=c1/2
r=c1
#p=(l+r)/2
#y=p*(c1-p)

while l<r:
	p=(l+r)/2
	y=p*(c1-p)
	if y==n:
		print (p)
		break
	if y>n:
		print (y>n)
		l=p
	else:
		print (y<n)
		r=p
		print ("done")
q=c1-p
print q
/*
if p>q:
    p,q=q,p
factor2 = 2021 * p + 2020 * q
if factor2 < 0:
    factor2 = (-1) * factor2
    
_P=sympy.nextprime(factor2)
*/