CTF密码学RSA专题

   个人在学习的过程中的一些新的感悟或者是遇到的一些问题记录下来供朋友们参考!以下所有类型都是遵循着先讲解再加自己编写的最基础脚本再附上解密代码来完成,都是些很基础的攻击方法。后面会循序渐进,不断加深。做这个工作其一是可以给我整理出更好的思路,其二也是方便更广大的基础薄弱的朋友们学习。以下的各个方向也有很多是本人从他处学习而来的,现在也希望做一个汇总。当然这里面也不乏有不对和不全面的地方,也诚心希望各位朋友们留言交流,指出问题,我们一起学习。 

一.RSA的加密系统

1.密钥的产生:

  (1)选两个保密的大素数p和q;

  (2)计算n=p*q,phi=(p-1)*(q-1)

    (3)  选一整数e,满足1<e<phi,且gcd(phi,e)=1

  (4)计算d,满足

              d*e=1 mod phi

    (5)以{e,n}为公钥,{d,n}为密钥

2.加密

  c = m^e \mod n

3.解密

m = c^d \mod n

以上是RSA的一整套系统,其中的欧拉定理,费马定理是后面密码攻击的基础,CTF很多关于RSA的题目都是非常灵活的,需要进行推导,而没有一概而论的方法。我们这次主要以记录的是RSA各种简单的攻击算法,其原理简单介绍,若有想详细了解的请参考一下博客:

RSA加密与解密原理-CSDN博客

二.dp泄漏攻击

2.1概述:

dp通常表示为dp=d%(p-1)的时候可以使用,但是这也不是百分百的通解,还是要根据数论的知识去推导,去分析,出题人很可能会把dp进行改变,下面就分享一下对于当dp=d%(p-1)时,应该如何进行求解。

2.2推导求解

一般而言,我们的e通常会选择65537,所以当e较小时,我们后面可以通过一个爆破的方法去求解。但是对于e很大的那种,比如:e=getprime(128),这样的e就不再适合这样最简单的求解方法。下面我们分别来看这两种情况

2.2.1e较小时

由dp=d%(p-1)得: 

        dp*e \equiv p*e \pmod{p-1} ,dp*e=d*e+k_1*(p-1).

由:e*d \equiv 1 \pmod{(p-1)*(q-1)}得:e*d=1+k_2*(p-1)*(q-1)

k2取正整数

因此便有:dp*e=1+k_2*(p-1)*(q-1)+k_1*(p-1)=k*(p-1)+1

   有些人可能会有疑问为什么这个公式到后面就变成了k*(q-1)了呢?k其实代表的是一个大的整数,实际上前面提取公因式的k2*(q-1)+k1其实就是一个大的整数,我们可以简化成用k来代替它,目的是为了更好的求q。现在dp已知,e已知并且比较小,因此我们就可以通过爆破10000-100000这个范围来找到p,如果e较大的话则就无法通过爆破来求解p。下面是求解p的一个脚本:

dp=
e=
for k in range(1000,100000):
     p=(dp*e-1)//k
     if n%p==0:
         q=n//p
         break
  示例:

  以下是我自己写的一个加密脚本:

import gmpy2
import libnum
from Crypto.Util.number import *
import sympy
from flag import *
p=getPrime(512)
q=getPrime(512)
m=bytes_to_long(flag1.encode('utf-8'))
e=65537
n=p*q
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
dp=d%(p-1)
c=pow(m,e,n)
print("n="+str(n))
print("dp="+str(dp))
print("c="+str(c))

n=81052209225131313507220713805700217491988454886746020895980749417178613600139235247258779197260341100869591606829313992848813324702809554723760154639427689733228864454366070581560556974998494011313369762319227600836884760639033725389112234871999044309896373724256975855908562623685006720904298827399627322201
dp=2333983257129874912385671201765238508018741076808932438041858359552286346278527293632610126948733391446892367533058183690279027737373912441118409764630447
c=68476292241229267501092370349201534535625199087078795295523172161118287687425619050390649353627256252410486411435512594156141345998060135460516702996170782196166408551565772171428275452466500295340890212477176167087348694002242090511244781979923679635724393516056094284607895043606608718749506531390556785422

就是给出了dp和e,我们用上面的解密脚本跑一下便能求解出flag:

n=81052209225131313507220713805700217491988454886746020895980749417178613600139235247258779197260341100869591606829313992848813324702809554723760154639427689733228864454366070581560556974998494011313369762319227600836884760639033725389112234871999044309896373724256975855908562623685006720904298827399627322201
dp=2333983257129874912385671201765238508018741076808932438041858359552286346278527293632610126948733391446892367533058183690279027737373912441118409764630447
c=68476292241229267501092370349201534535625199087078795295523172161118287687425619050390649353627256252410486411435512594156141345998060135460516702996170782196166408551565772171428275452466500295340890212477176167087348694002242090511244781979923679635724393516056094284607895043606608718749506531390556785422
e=65537
for k in range(1000,100000):
    p=(dp*e-1)//k+1
    if n%p==0:
        q=n//p
        break
phi1=(p-1)*(q-1)
d=gmpy2.invert(e,phi1)
m=pow(c,d,n)
print(long_to_bytes(m))

2.2.2e较大时

  当e的值比较大的时候就不能直接通过爆破来求p和q的值了,只能通过数学计算的方式,不过也能求

  前面已知:a^{dp*e} \equiv a^{k(p-1)+1} \mod p成立,则由费马小定理可以得到:a^{dp*e} \equiv a \mod n,(费马定理不会的需要自己查阅,这个很基础)。

下面证明a^{dp*e} \equiv a \mod n成立。则接下来变形之后有:

a^{dp*e}=x+k*n=x+k*pq(先用x代替a)

两边分别模p得到:

a^{dp*e} \mod p= x \mod p =a

(因为最左边的mod与a相等,所以连等),因此a^{dp*e}=a+k*n,因此最初假设成立。得到:

p=gcd(a^{dp*e} mod n-a,n),   p就可以求解出来,进而求q从而得到答案。解决该问题的代码其实主要就是通过求最大公因数求出p,其他倒没什么比较简单。

       示例:

下面也是我自己写的一个加密脚本:

import gmpy2
import libnum
from Crypto.Util.number import *
import sympy
from flag import *
p=getPrime(512)
q=getPrime(512)
m=bytes_to_long(flag5.encode('utf-8'))
e=getPrime(2048)
n=p*q
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
dp=d%(p-1)
c=pow(m,e,n)
print(e)
print("n="+str(n))
print("dp="+str(dp))
print("c="+str(c))


20950948418166749541770929901599456845042487336084250259044505124039357201519390955903239984342840418722906693682912236192729161546222882788867985662022028428952553430263930201949472926593118263456614357149804075623298269236922972634996801480169456797981627628421980443667940376221293938360839979825536953237734395003820847098057452380131659048080029304257135718859118812657018251580111940718343231260859682795439495348790143273834190049443917864134591155630265427621410894151291942735983613925657475086784177691811174559026423441330728645779421236940535885008469066203286106601122629423036760187558805398329789556673
n=90796163340109588564738788384463894983912128727430649948607473671104178602208277395285591338582997759466459158893050336561033490678560950844531586374985867823184348027168367430161712168150464964853158372149309548273864366715650385471973848215178560028489299900011829936565389876913963266353944273327225934159
dp=141664857460561114973420341283446523340594059943806593358302977072396108637613660756110448057067294020686239665775469495045711784251758578309493537756167
c=88729128452395933112703633295691610114549957546702482193005139242650792780079131155565179465219090581483292108240381305652410720006154267820639745713883392648077265723279918993512760841882378003443941115371217980920861422918748408667891054465229464951072058730238785299043133827034382011406455107402548547862

下面是解密脚本:

import gmpy2
from Crypto.Util.number import *
e=
n=
dp=
c=
a=pow(2,dp*e,n)-2
def ext_gcd(a, b): #扩展欧几里得算法
    if b == 0:
        return 1, 0, a
    else:
        x, y, gcd = ext_gcd(b, a % b) #递归直至余数等于0(需多递归一层用来判断)
        x, y = y, (x - (a // b) * y) #辗转相除法反向推导每层a、b的因子使得gcd(a,b)=ax+by成立
        return x, y, gcd

_,_,p=ext_gcd(a,n)
q=n//p
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(long_to_bytes(m))

三.素数生成问题攻击

3.1概述

  跟个人认为,素数生成问题是一个十分宽泛的概念,它是从密钥生成的源头上,p和q来整活。因此,这种的也必须有良好的数论基础,能够做到自己推导,现在主要就是讲解下思维,举一些比较常见的例子。

3.2|p-q|相差小

     因为|p-q|小,所以{(p-q)}^2也小。因此通过把它展开可以得{(p-q)}^2={(p+q)}^2-4*p*q={(p+q)}^2-4*n

等式两边再变换亦可以得到:\frac{(p-q)^2}{4}=\frac{(p+q)^2}{4}-n=(\frac{p-q}{2})^2

x=\frac{p+q}{2},y=\frac{p-q}{2}得到:x^2-n=y^2

此时我们遍可以先暴力破解x,通过这个方程计算y是否是一个平方数从而得到想要的结果

3.3.题目:XYCTF happy_to_solve

下面是题目所给的加密脚本:

from Crypto.Util.number import *
import sympy
from secrets import flag


def get_happy_prime():
    p = getPrime(512)
    q = sympy.nextprime(p ^ ((1 << 512) - 1))
    return p, q


m = bytes_to_long(flag)
p, q = get_happy_prime()
n = p * q
e = 65537
print(n)
print(pow(m, e, n))
# 24852206647750545040640868093921252282805229864862413863025873203291042799096787789288461426555716785288286492530194901130042940279109598071958012303179823645151637759103558737126271435636657767272703908384802528366090871653024192321398785017073393201385586868836278447340624427705360349350604325533927890879
# 14767985399473111932544176852718061186100743117407141435994374261886396781040934632110608219482140465671269958180849886097491653105939368395716596413352563005027867546585191103214650790884720729601171517615620202183534021987618146862260558624458833387692782722514796407503120297235224234298891794056695442287

先解释下这个脚本的意思:可以看到,(1<<512)-1相当于是512个1,然后再与p进行异或操作,则是对p进行取反。取反之后再取下一个素数即为q。由此我们可以看出2^{511}<p,q<2^{512},故可以从1<<511开始进行往后进行爆破。下面是脚本:

a=1<<511
while True: 
 a+=1
 B2=pow(a,2)-n
 if gmpy2.is_square(B2):
 b=gmpy2.iroot(B2,2)[0]
 p=a+b
 q=a-b
 assert n==p*q
 assert q == nextprime(p ^ ((1 << 512) - 1)) or p == nextprime(q ^ ((1 
<< 512) - 1))
 phi = (p-1) *(q-1)
 d = inverse(e,phi)
 m = pow(c,d,n)
 print(long_to_bytes(m))
 break

只要找到了一个合适的p和q就可以打印出来。

四.m变换攻击

4.1概述

   m变换即为不对m进行加密而是对m先进行某个方式处理之后的M进行加密,这通常也是没有一个固定的思路,但是我的话做这种题基本上是通过找到跟n有相同公因数的数来进行分解,依次来求出p和q进而再攻破整个RSA系统。

4.2示例:

下面举一个小例子跟大家说明,但是也只是提供一个数论推导的思路,如果遇到其他题目还是要灵活变通。

下面是我自己写得一个加密脚本:

import gmpy2
import libnum
from Crypto.Util.number import *
import sympy
from flag import *
m=bytes_to_long(flag6.encode('utf-8'))
p=getPrime(512)
q=getPrime(512)
e=65537
n=p*q
M=(m*e*p)
c=pow(M,e,n)
print("c="+str(c))
print("n="+str(n))




c=39676775216830987547497948105039196964298153604883463197959175030825778407812898515137484044066412273138993759491138498311400681417796151246155610179295947970506468305676665593938899079942631282200403399806694273425377530358565670700840445343744902031231823964875429755414701997004881922169592185473288470809
n=73644742376807333410520915386873319455654279573143580323970430328813412259121243081760274126804644836401388338692111584951368272268616700951990756173537234655419309440920809165507341134588439257898382188574694307269757122072160684834379347322628879664563907932663731527472331639089143447532781861031363702371

由于M=m*e*p未对其取模,因此相对叫=较为容易些。

经过分析得到:

    c \equiv M^e \mod n=c \equiv (m*e*p)^e \mod n=c \equiv k*p \mod n

(最后一组式子是因为前面m*e*p含有p所以不论几次乘方都是p的倍数,因此用k*p来表示)

再由最右边的式子变形得到:c-k1*p=k*n

再变形得

c=k1*p+k*n=k1*p+k*p*q=p*(k1+k*q)由此我们可得c是p得倍数,而n也是p得倍数,因此我们就可以通过通过求出n和c的最大公因数来求p,进而求q,这样问题就解决了。

下面是上述加密代码解密的脚本:

c=39676775216830987547497948105039196964298153604883463197959175030825778407812898515137484044066412273138993759491138498311400681417796151246155610179295947970506468305676665593938899079942631282200403399806694273425377530358565670700840445343744902031231823964875429755414701997004881922169592185473288470809
n=73644742376807333410520915386873319455654279573143580323970430328813412259121243081760274126804644836401388338692111584951368272268616700951990756173537234655419309440920809165507341134588439257898382188574694307269757122072160684834379347322628879664563907932663731527472331639089143447532781861031363702371
from Crypto.Util.number import *
import gmpy2
import libnum

p=libnum.gcd(c,n)
q=n//p
phi=(p-1)*(q-1)
e=65537
d=libnum.invmod(e,phi)
M=pow(c,d,n)
m1=M//e
m=m1//p
print(long_to_bytes(m))

如果是在M后面加个模数,暂时还不会怎么解决,如果有方法的可以一起交流。

五.共模攻击

5.1概述

  这类题通常是RSA的模数一样,但是选取的公钥不一样,从而导致加密得到的密文也不一样,当e1与e2互素时,我们便通过拓展的欧几里得算法求解,下面介绍推导的方法

5.2 gcd(e1,e2)=1

c_1 \equiv m^{e_1} \pmod{n}\\ c_2 \equiv m^{e_2} \pmod{n}

由欧几里得算法我们可以直到,存在r,s使得:r*e_1+s*e_2=1,将上述同余方程组变形可得:

c_1^r*c_2^s=m^{r*e_1+s*e_2} \equiv m \pmod{n},这种攻击比较简单,下面直接看我写的一个加密脚本再来进行解密。

import gmpy2
import libnum
from Crypto.Util.number import *
import sympy
from flag import *
p=getPrime(512)
q=getPrime(512)
m=bytes_to_long(flag1.encode('utf-8'))
n=p*q
e1=31
e2=29
c1=pow(m,e1,n)
c2=pow(m,e2,n)
print(m)
print(n)
print(c1)
print(c2)

m=777244835068357311911032869770271324564046255649395818204920650673780153257661330813
n=113933924865156614349527403279757330113062237902524878583579143169889645168522963200510456761439511787161855026761069382817658265417241249608142805204068893391919549388603175253381266484531797939234152123571413005974111522941366890097566032318148920279454928949689306856928907084524996429264333157623010930659
c1=107613033986333629842611883878185740927303655561490670046308173233483055164088478999163750086999429829068424963146157347169120120228795179852642784470170643379316934670569935651161912459887413128861055185896666120721305533550531864131041791354171498098226177134390372183756392657343389351059413900699833362336
c2=18676460392497165526956780886077489822381152127789557928572839875244401805303859802632160438421671031377440341280265038335652283004250140937340472134513027943388172351071303693544926761321794507751567593096811289112667662641230343640579787353200858583785496440842072230574245010532077110090073462970765529325

下面是解密脚本:(只提供思路,其余细节按照理论自己补充以下,有问题随时留言)

def ext_gcd(a, b): #扩展欧几里得算法
    if b == 0:
        return 1, 0, a
    else:
        x, y, gcd = ext_gcd(b, a % b) #递归直至余数等于0(需多递归一层用来判断)
        x, y = y, (x - (a // b) * y) #辗转相除法反向推导每层a、b的因子使得gcd(a,b)=ax+by成立
        return x, y, gcd
r,s,_=ext_gcd(e1,e2)
m1= (pow(c1, r, n) * pow(c2, s, n)) % n
print(m1)
print(long_to_bytes(m1))

   

e1,e2不互素的情况还不是很会,以后会继续学习继续更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值