RSA攻击:模数分解

目录

一、模数分解总览

        1.1直接分解法

        1.2费马分解与Pollard_rho分解

        1.3公约数分解

        1.4其他模数分解

二、实战特训

        2.1[黑盾杯 2020]Factor

       2.2[GWCTF 2019]babyRSA

      2.3[LitCTF 2023]yafu (中级)

        2.4[RoarCTF 2019]RSA

      2.5[CISCN 2022 西南]rsa

三、总结


一、模数分解总览

        1.1直接分解法

           在RSA的通信流程中,对外保密的是私钥,但是究其本质对外隐藏的是大素数P,Q。而正是大数分解难题的原因,使得即使暴露N,即PQ之积的值暴露,破解难度也依旧很大。所以,我们只需要掌握缺失的信息P,Q即可完成解密

          但是如果N值取的很小,我们通常可以进行暴力分解,从而获取P,Q。顺带一提,在工业中我们认为2048bit以上的N是安全的。但是,在CTF竞赛中,我们几乎不会遇到2048bit以上的素数分解。所以,这里我们可以放心使用。稍后,笔者会给处自己对与N很小的理解。

          使用条件:在阅读部分书籍的密码学(crypto)部分的解析,以及一些题目经验,大部分适合直接数模分解的N一般小于512bit。该使用条件,是笔者的拙见,欢迎大家在评论区讨论。

        1.2费马分解与Pollard_rho分解

          在1.1中我们提及了,如果N值很小,那么我们可以直接分解。但是如果N值大,我们将寸步难行。可是,这种难度在于直接分解,而不是利用部分数学技巧与原理。

          例如在费马分解中,当P,Q两个值过于接近,也就是P-Q的值很小。此时,我们就可以令A = (P + Q) / 2, B = (P - Q) / 2 则 N = A^2 - B^2。其中,在具体算法实现的过程中,因为P - Q很小,所以我们可以枚举爆破。而且根据 N = PQ,且N值一般已知,就有 A^2 = B^2 + N。

          而在Pollard_rho分解中,则恰恰相反。该分解说明当P,Q相差过大时,可以被暴力分解。从费马分解的角度说,如果P - Q过大,那么P + Q就会过小。因此可以枚举P + Q暴力破解。然而该分解方式有一定的数学原理(本菜狗没学原理,赶比赛就停在应用层面先)。

          Pollard算法的原理大体是通过某一种方式获取得到A,B值。计算p = gcd(a - b, n),直到p不为1,或者a,b出现循环。返回一个“因子”p。接着,我们可以递归的计算Pollard(p)与Pollard(n/p),值得一提得是我们p == n时,返回的就是n是一个质数退出。一般我们认为B = A^2 + 1。

          这个方法很美好,但是显示很骨感。这种分解方式建立于我们知道P - Q的状态。然而,现实和比赛中,P、Q都是被隐藏的,所以我们很难判断是否可以使用这种方式破解。SO!我们只能抱着尝试的方法使用该方法

        1.3公约数分解

          公约数分解一般是建立在多组信息(密文)在加密过程中,采取了相同的大素数P。因此,我们可以通过gcd来获取一个因此P,进而得知另外一个因子。

          使用条件:出现多组密文。

        1.4其他模数分解

          一般这种分解方式也是最难的,因为会考察选手的数学能力。也就是题目会给出一些额外的数学表达式,选手根据表达式合理爆破。或者我们可以使用factordb.com进行分解。但是当数字过大时,网站会显示补全,hhh。

          使用条件:当上述方式失效,或者题目给出额外的数学式子

二、实战特训

        2.1[黑盾杯 2020]Factor

          点击这里,跳转至题目

          在本道题中,我们获取得到了一下信息,如图所示。

          这道题有许多的尝试方法,如下:

          1.因为n比较小,可以直接分解n

          2. 因为e比较小以及给出了其他的数学关系,我们可以使用小加密明文爆破+数学关系判断。

          相对来讲,分解n更为简单。所以,我们优先尝试。注意:密码学更多的是尝试,而不是一眼定方法。使用yafu工具,我们获取一下因子。

          因此,我们可以编写一下代码段。

from Crypto.Util.number import *
import sympy
import primefac
from libnum import n2s
import gmpy2
import wienerAttack

n = 3454083680130687060405946528826790951695785465926614724373
e = 3
c = 1347530713288996422676156069761604101177635382955634367208
# gcd(m, n) = 1

p = 17100682436035561357
q = 17172929050033177661
r = 11761833764528579549

phi = (q - 1) * (r - 1) * (p - 1)
d = primefac.modinv(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

           但是,我们在运行时,发现代码报错。别急不是因为你错了,可能是因为出现了“假素数”,即(某一大素数 - 1) 是 e 的倍数。因此经过排查,发现(p - 1) % e == 0。因此剔除p。修改代码得到。

from Crypto.Util.number import *
import sympy
import primefac
from libnum import n2s
import gmpy2
import wienerAttack

n = 3454083680130687060405946528826790951695785465926614724373
e = 3
c = 1347530713288996422676156069761604101177635382955634367208
# gcd(m, n) = 1

p = 17100682436035561357
q = 17172929050033177661
r = 11761833764528579549

phi = (q - 1) * (r - 1)
d = primefac.modinv(e, phi)
m = pow(c, d, q * r)
print(long_to_bytes(m))

          最后获取旗帜FLAG{3_RSA}。

       2.2[GWCTF 2019]babyRSA

          点击这里,跳转至题目

          根据题目附件,我们可以获取得到以下信息(未全部显示)。

          以及以下数学式:c1 = F1 + F2, c2 = F1^3 + F2^3。以及p < q。

          在这里我们获取到的信息是n值大概在2048bit。而且给出其他表达式,所以我们尝试其他数模分解法。

          首先,我们明确思路。我们需要获取c1, c2来计算得到F1、F2。然后,将F1、F2转职为字符串即可。而获取c1、c2我们需要获取p,q。所以,现在最大的难题在于p,q。在这里说明两种方式。

          方法1:关注到p,q是相邻的两个素数,如果你知道素数分布规律,即他们之间相差lnx。所以我们可以判断出 q - p 大约在1000。所以我们可以使用费马分解

          使用yafu分解得到以下结果。

         方法2(推荐掌握): 根据p < q的关系,我们可以判断出 n > p^2,所以sqrt(n) > p。所以,我们可以猜想 nextprime(sqrt(n)) == nextprime(p) == q。证明如下:

          n = n'^2 = pq = (n''^2 - d^2) ==> n'' = (p + q) / 2 > n' > p,也就是说 n' - p < q - p.建议画一个数轴理解。

import gmpy2
import sympy

N=636585149594574746909030160182690866222909256464847291783000651837227921337237899651287943597773270944384034858925295744880727101606841413640006527614873110651410155893776548737823152943797884729130149758279127430044739254000426610922834573094957082589539445610828279428814524313491262061930512829074466232633130599104490893572093943832740301809630847541592548921200288222432789208650949937638303429456468889100192613859073752923812454212239908948930178355331390933536771065791817643978763045030833712326162883810638120029378337092938662174119747687899484603628344079493556601422498405360731958162719296160584042671057160241284852522913676264596201906163

n = gmpy2.iroot(N, 2)[0]
q = sympy.nextprime(n)
p = n // q

          运行结果如下:其中1038 == q - p --> 验证素数分布,方法1

 

          两个方法没有优劣之分,知识方法二更贴合题意。方法一更快,但是需要我们判断p,q对应的数字。所以,按需对应选择。

          接下来获取c1,c2就简单了。

imoprt primefac

phi = (p-1)*(q-1)
d = primefac.modinv(e, phi)

c1 = powmod(m1, d, N)
c2 = powmod(m2, d, N)

        接下来获取F1,F2。构造二次方程x^2 - (F1 + F2)x + F1F2 = 0。使用c1,c2表示就为x^2 - c1x + (c1^2 - c2/c1)/3 = 0

A = gmpy2.mpz(1)
B = gmpy2.mpz(-c1)
C = gmpy2.mpz((c1*c1 - c2//c1)//3)
delta = gmpy2.mpz(gmpy2.iroot(B*B - 4*A*C,2)[0])
F1 = (-B - delta) // 2
F2 = (-B + delta) // 2

flag1 = long_to_bytes(F1)
flag2 = long_to_bytes(F2)
print(flag1 + flag2, flag2 + flag1)

          得到旗帜FLAG{f709e0e2cfe7e530ca8972959a1033b2}

          当然你也可以使用sympy.solve来获取根。

      2.3[LitCTF 2023]yafu (中级)

        点击这里,跳转至题目

        这道题比较简单,题目就提示了使用yafu。一共会获取15个因子,这道题额外考察了欧拉函数的性质问题。解决代码如下。

p1=2151018733
p2=2201440207
p3=2315495107
p4=2585574697
p5=2719600579
p6=2758708999
p7=2767137487
p8=2906576131
p9=2923522073
p10=3354884521
p11=3355651511
p12=3989697563
p13=4021078331
p14=4044505687
p15=4171911923


phi = (p1 - 1) * (p2 - 1) * (p3 - 1) * (p4 - 1) * (p5 - 1) * (p6 - 1) * (p7 - 1) * (p8 - 1) * (p9 - 1) * (p10 - 1) * (p11 - 1) * (p12 - 1) * (p13 - 1) * (p14 - 1) * (p15 - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

        2.4[RoarCTF 2019]RSA

          点击这里,跳转至题目

         我们可以获取信息如下。

          在这里,我们知道P、Q可能相差过大,同时我们也获取了额外信息。所以我们可以尝试使用Pollard和其他数模分解法。

          尝试Pollard,发现预估等待时间 >= 1h。果断停止尝试。

          开始尝试其他数模分解。关注到有2019次方,所以枚举x,y的范围不会特别大,我们可以接受。

A =  2683349182678714524247469512793476009861014781004924905484127480308161377768192868061561886577048646432382128960881487463427414176114486885830693959404989743229103516924432512724195654425703453612710310587164417035878308390676612592848750287387318129424195208623440294647817367740878211949147526287091298307480502897462279102572556822231669438279317474828479089719046386411971105448723910594710418093977044179949800373224354729179833393219827789389078869290217569511230868967647963089430594258815146362187250855166897553056073744582946148472068334167445499314471518357535261186318756327890016183228412253724

for x in range(1, 1000):
    for y in range(1, 1000):
        D = x % y
        if (D != 0) :
            f=(((y%x)**5)%D)**2019+y**316+(y+1)//x
            if (f == A) :
                print(x, y)
                break

        获得x = 2, y = 83

        然后我们关注到以下两个表达式:

          因此n = p * q > x*y*z^2, 也就是 n / x / y > z^2,那我们可以像2.2那样,获取q

n =  117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127

x = 2
y = 83

n1 = n // 166 #可以存放
q = sympy.nextprime(gmpy2.iroot(n1, 2)[0])
p = n // q
assert isPrime(q) and isPrime(p)

print("OK") #确认步骤2正确
print(p, q)

        获得p,q后,我们就可以开始正是解码

c =  41971850275428383625653350824107291609587853887037624239544762751558838294718672159979929266922528917912189124713273673948051464226519605803745171340724343705832198554680196798623263806617998072496026019940476324971696928551159371970207365741517064295956376809297272541800647747885170905737868568000101029143923792003486793278197051326716680212726111099439262589341050943913401067673851885114314709706016622157285023272496793595281054074260451116213815934843317894898883215362289599366101018081513215120728297131352439066930452281829446586562062242527329672575620261776042653626411730955819001674118193293313612128

e = 0x10001 # e = 65537
phi = (p - 1) * (q - 1)
d = primefac.modinv(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

          获取旗帜FLAG{wm-l1l1ll1l1l1l111ll}

      2.5[CISCN 2022 西南]rsa

          点击这里,跳转至题目

          题目附件内容如下:

from Crypto.Util.number import *
import gmpy2

flag = b'XXXXXXXX'
p1 = getPrime(700)
r1 = getPrime(700)
for i in range(10):
    q1 = 5*p1+i
n = p1*q1*r1
p3 = pow(p1,3,n)
q3 = pow(q1,3,n)

print(p3)
print(q3)
'''
p3 = 29914513810588158800677413177910972738704129106546850855032986405861482276089830788972187432277517348644647399654780884571794069905291936470934226328931651386658328163535027343107140438177837479649822914209171476632450951930287641742344330471734177295804718555774395704231261550376220154493373703096062950390869299905383682611063374747752091585836452902373843865043412096365874638466683035848817858586173172058756256354758712684819253211761289032789542371351760915771791997388241121078055468403109260493642435791152671979552597191217179672328555740595434990908530985477314228867209314472001848844089467987561661918366232980944933533
q3 = 66208618374366130551979192465001581263127328176551695213970812805980115496523825511250542987452691413485117902772315362811067501379171731387904074565035353566976164797769439898266222919741874340315356585585077141595328441423323822407738375537476582506440045835592730211502035261968878999959340204806442390319739977816872969200022096331677277225467021553564212725120939434924481787524609852608476848761521446441776154400518315701988027274150425936061679275540502720782853648148897480117033152064922234451671636288396704170234613549011854618414776342798740690128675106027908639984431432591397555541420243824539205614036979987830125678
'''
P = getPrime(1024)
Q = getPrime(1024)
N = P * Q
E = 65537
lcm = gmpy2.lcm(P-1, Q-1)
e1 = gmpy2.invert(p1, lcm)
e2 = gmpy2.invert(r1, lcm)
m = bytes_to_long(flag)
c = pow(m, E, N)

print(lcm)
print(c)
print(N)
'''
lcm = 4292158730589770192682795435047249488185453170529228019750042608688907718268448193363838203887391025871515871000364259326343790645215256385842265899206372365402431198699714374850409466996627163968391249416054093529090485677808301343590811445080871279796162536469847469761747058736980603093722710824453312207182881241846080117790728778291633761198069016865260030288832065807438020772711645648333908622890343009942617559434851450007195025869850769670769715654662127278293639938359741401336592219730356884542179574372134014927006215640945952229142436595334916765255426954857520777553915330597952622785359222832224632624
c = 4288727484183191191687364666620023549392656794153112764357730676861570386983002380982803054964588111708662498647767438881892355599604826306427809017097724346976778230464708540600157055782723189971534549543664668430013171469625043063261219462210251726207552819381767396148632877168530609902046293626355744288863460554297860696918890189350721960355460410677203131993419723440382095665713164422367291153108363066159712951217816814873413423853338021627653555202253351957999686659021298525147460016557904084617528199284448056532965033560516083489693334373695545423561715471204868795248569806148395196572046378679014697206
N  = 17168634922359080770731181740188997952741812682116912079000170434755630873073792773455352815549564103486063484001457037305375162580861025543369063596825489461609724794798857499401637867986508655873564997664216374116361942711233205374363245780323485119184650145879389879046988234947922412374890843297813248828996855478005656041814919367820336728271583686844991928889831691815821365423570311291064846736832327637944358854661523107817781673029406341843040857813841671405147146887291204140157388049394514390098066284975682117038362207142272098796924412602725857521665773622056312191400612944442008222587867782281556388669
'''

          这道题纯吓人,因为密文跟p3,q3没关系。所以我们不需要用到它。注意的是,这里phi用lcm来表示了。

d = primefac.modinv(E, lcm)
m = pow(c, d, n)
primt(long_to_bytes(m))

          获得FLAG{h3ll0_wo21d!}

三、总结

          在这里,涵盖了大部分的模数分解法的题目做法。我们分析了如何判断以及尝试的优先级。如何根据代码报错,进行实时的调整、排错。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我之前提供的示例代码有误。在Qt中,没有名为"Rsa"的成员函数。正确的方法是使用`QSslKey::Algorithm`枚举来指定算法类型。 以下是更新后的示例代码: ```cpp #include <QCoreApplication> #include <QSslSocket> #include <QSslKey> #include <QSslCertificate> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 生成RSA私钥 QSslKey privateKey(QSslKey::Rsa, 2048); if (!privateKey.isNull()) { // 生成证书签发请求 QSslCertificate csr; csr.setSubject(QSslCertificate::commonName(), "Your Common Name"); csr.setSubject(QSslCertificate::organizationName(), "Your Organization Name"); csr.setSubject(QSslCertificate::organizationalUnitName(), "Your Organizational Unit Name"); csr.setSubject(QSslCertificate::emailAddress(), "Your Email Address"); csr.setSubject(QSslCertificate::localityName(), "Your Locality Name"); csr.setSubject(QSslCertificate::stateOrProvinceName(), "Your State or Province Name"); csr.setSubject(QSslCertificate::countryName(), "Your Country Name"); csr.setPublicKey(privateKey.toPublicKey()); csr.sign(privateKey, "sha256"); // 生成自签名证书 QSslCertificate selfSignedCert(csr); selfSignedCert.setIssuer(selfSignedCert.subject()); selfSignedCert.sign(privateKey, "sha256"); // 保存私钥和证书到文件 QFile privateKeyFile("private.key"); if (privateKeyFile.open(QIODevice::WriteOnly)) { privateKeyFile.write(privateKey.toPem()); privateKeyFile.close(); } QFile certFile("certificate.crt"); if (certFile.open(QIODevice::WriteOnly)) { certFile.write(selfSignedCert.toPem()); certFile.close(); } qDebug() << "自签名证书生成成功!"; } return a.exec(); } ``` 请注意,这段代码使用了Qt的`QSslKey::Rsa`成员来指定RSA算法类型,用于生成私钥。如果您仍然遇到问题,请检查您的Qt版本和相关库的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值