• RSA:基于小加密指数的攻击方式与思维技巧


    目录

    目录

    目录

    零、前言

    一、小加密指数爆破

    [FSCTF]RSA签到

    思路:

    二、基于小加密指数的有限域开根

    [NCTF 2019]easyRSA

    思路:

    三、基于小加密指数的CRT

    [0CTF 2016] rsa

    思路:


    零、前言

        最近,发现自己做题思路比较混乱。总的来说,就是在各种方法之间很难适配到对应的题目。所以,写下这篇博客来记录这些区别。特别说明的是,这篇文章更偏向于解题,而不是讲解原理。考虑到两个点,在写下这篇博客时本人其实也才学习了近1个月的密码学,数学知识严重匮乏,不敢乱教与解析原理。其次,备战省赛在即没有充分多的时间让我去了解学习深层次的原理。所以这里只能够给出使用条件,也就是应用层面上的区分。

        此外特别声明,该篇博客更多的偏向于个人学习使用,其次是帮助大家应用。再者也欢迎各位指出错误,与提出问题。本人会在能力范围内尽可能作答。

    一、小加密指数爆破

        小加密指数爆破是最为简单的求解方式。几乎遇到小加密指数都可以尝试一下。因为它使用条件最为简单:加密指数小需要注意的是,又是时候我需要分析数据特征。例如分析出flag比较短,即密文c很小时。我们可以优先直接开e次方。这一技巧出现于FSCTF中,这能帮助我们剔除混淆视听的提示--干扰信息。

    [FSCTF]RSA签到

    1. from Crypto.Util.number import *
    2. from secret import flag
    3. m = bytes_to_long(flag)
    4. assert m.bit_length()<150
    5. p = getPrime(512)
    6. q = getPrime(512)
    7. n = p*q
    8. e = 3
    9. c = pow(m, e, n)
    10. kbits = 103
    11. m = (m >> kbits) << kbits
    12. Mod = getPrime(2048)
    13. hint1 = (2019-2023*m) % Mod
    14. hint2 = pow(2, 2023, Mod)
    15. print('n =',n)
    16. print('c =',c)
    17. print('hint1 =',hint1)
    18. print('hint2 =',hint2)
    19. '''
    20. n = 113369575322962228640839640796005129142256499725384495463316595604047079557930666699058024217561098997292782305151595366764483672240871690818579470888054811186902762990032505953330034837625667158114251720321766235335996441613828302393569643827293040591156144187232255906107532680524431761932215860898533224303
    21. c = 42336544435252811021843650684098817755849747192874682997240960601474927692351510022965782272751339319782351146077580929125
    22. hint1 = 23620186624579054670890922956929031966199853422018331906359817627553015939570302421768667351617160816651880338639432052134891008193969801696035505565684982786461527274477933881508678074157199742425764746919878452990468268098540220237611917321213668069666526658025737487539455262610713002399515462380573732082344497124344090365729168706760425585735014513373401622860196569544933971210142724734536588173957576667830667503151362930889494877201597267000737408071228466811160470759093928003064486766171850080985758351203536462206720715743059101285822169971058423075796415932349942113371706910521251120400151508125606778268
    23. hint2 = 963121833542317369601573845406471251262548645428284526828835768327851746644612875378048462019053502788803516653832734212104068969204751285764221918179043624419894139984279754512017898273159626328827668380262481220865017731267802600915375183179264380651165421367773563947903391466768557089792263481734108493385146063258300495764165365295546337808852673629710735621386935094923561594142327134318905856137785813985574356271679918694447015294481691849341917432346559501502683303082591585074576786963085039546446281095048723669230856548339087909922753762884060607659880382812905450025751549153093939827557015748608
    24. '''

    思路:

    通过肉眼观察,我们也能发现 密文(c) << 模数(n)

    1. import gmpy2
    2. from Crypto.Util.number import *
    3. n = 113369575322962228640839640796005129142256499725384495463316595604047079557930666699058024217561098997292782305151595366764483672240871690818579470888054811186902762990032505953330034837625667158114251720321766235335996441613828302393569643827293040591156144187232255906107532680524431761932215860898533224303
    4. c = 42336544435252811021843650684098817755849747192874682997240960601474927692351510022965782272751339319782351146077580929125
    5. '''
    6. print(n.bit_length())
    7. print(c.bit_length())
    8. n.bit_length() = 1024
    9. c.bit_length() = 405
    10. '''
    11. if (gmpy2.iroot(m, 3)[1]):
    12. print(gmpy2.iroot(m, 3)[0]) # m = 34852863801144743432974618956978703253885
    13. m = 34852863801144743432974618956978703253885
    14. print(long_to_bytes(m)) # flag{sign_1n_RSA}

    二、基于小加密指数的有限域开根

        实际上,有限域上的开根并不需要有小加密指数的限制。指数当指数较低的时候运算速度会快一点

        有限域上的开根条件为:e | phi,且 e  | 任意因子的欧拉函数

    [NCTF 2019]easyRSA

    1. from flag import flag
    2. e = 0x1337
    3. p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
    4. q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741
    5. n = p * q
    6. assert(flag.startswith('NCTF'))
    7. m = int.from_bytes(flag.encode(), 'big')
    8. assert(m.bit_length() > 1337)
    9. c = pow(m, e, n)
    10. print(c)
    11. # 10562302690541901187975815594605242014385201583329309191736952454310803387032252007244962585846519762051885640856082157060593829013572592812958261432327975138581784360302599265408134332094134880789013207382277849503344042487389850373487656200657856862096900860792273206447552132458430989534820256156021128891296387414689693952047302604774923411425863612316726417214819110981605912408620996068520823370069362751149060142640529571400977787330956486849449005402750224992048562898004309319577192693315658275912449198365737965570035264841782399978307388920681068646219895287752359564029778568376881425070363592696751183359

    思路:

    首先我们能够看到 e = 0x1337 < 0x10001,算是比较小的一个加密指数。因此我们考虑一些基于小加密指数的攻击。但是因为这里 e = 0x1337 虽然算小,但是对于开方运算来说还是比较大的。因此我们不打算尝试小加密指数爆破。

    因此我们似乎只能分析其他攻击路径。那么我开始尝试有限域开根(可以思考一下,为什么后续攻击也可以不在考虑范围内,这样更真实的还原了做题的情形)。

    所以我们先分析是否满足我们的使用条件。如果直接满足就是脚本题了。否则就需要一些处理操作。

    1. e = 0x1337
    2. p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
    3. q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741
    4. n = p * q
    5. print((p - 1)*(q - 1) % e) # 0
    6. print((p - 1) % e) # 0
    7. print((q - 1) % e) # 0

    通过测试程序,我们可以确定可以使用有限域开根。因此有以下脚本。

    1. from gmpy2 import *
    2. from Crypto.Util.number import *
    3. import random
    4. import math
    5. def onemod(e, q):
    6. p = random.randint(1, q-1)
    7. while(powmod(p, (q-1)//e, q) == 1): # (r,s)=1
    8. p = random.randint(1, q)
    9. return p
    10. def AMM_rth(o, r, q): # r|(q-1
    11. assert((q-1) % r == 0)
    12. p = onemod(r, q)
    13. t = 0
    14. s = q-1
    15. while(s % r == 0):
    16. s = s//r
    17. t += 1
    18. k = 1
    19. while((s*k+1) % r != 0):
    20. k += 1
    21. alp = (s*k+1)//r
    22. a = powmod(p, r**(t-1)*s, q)
    23. b = powmod(o, r*a-1, q)
    24. c = powmod(p, s, q)
    25. h = 1
    26. for i in range(1, t-1):
    27. d = powmod(int(b), r**(t-1-i), q)
    28. if d == 1:
    29. j = 0
    30. else:
    31. j = (-math.log(d, a)) % r
    32. b = (b*(c**(r*j))) % q
    33. h = (h*c**j) % q
    34. c = (c*r) % q
    35. result = (powmod(o, alp, q)*h)
    36. return result
    37. def ALL_Solution(m, q, rt, cq, e):
    38. mp = []
    39. for pr in rt:
    40. r = (pr*m) % q
    41. # assert(pow(r, e, q) == cq)
    42. mp.append(r)
    43. return mp
    44. def calc(mp, mq, e, p, q):
    45. i = 1
    46. j = 1
    47. t1 = invert(q, p)
    48. t2 = invert(p, q)
    49. for mp1 in mp:
    50. for mq1 in mq:
    51. j += 1
    52. if j % 1000000 == 0:
    53. print(j)
    54. ans = (mp1*t1*q+mq1*t2*p) % (p*q)
    55. if check(ans):
    56. return
    57. return
    58. def check(m):
    59. try:
    60. a = long_to_bytes(m).decode('utf-8')
    61. if 'NCTF' in a:
    62. print(a)
    63. return True
    64. else:
    65. return False
    66. except:
    67. return False
    68. def ALL_ROOT2(r, q): # use function set() and .add() ensure that the generated elements are not repeated
    69. li = set()
    70. while(len(li) < r):
    71. p = powmod(random.randint(1, q-1), (q-1)//r, q)
    72. li.add(p)
    73. return li
    74. if __name__ == '__main__':
    75. c = 10562302690541901187975815594605242014385201583329309191736952454310803387032252007244962585846519762051885640856082157060593829013572592812958261432327975138581784360302599265408134332094134880789013207382277849503344042487389850373487656200657856862096900860792273206447552132458430989534820256156021128891296387414689693952047302604774923411425863612316726417214819110981605912408620996068520823370069362751149060142640529571400977787330956486849449005402750224992048562898004309319577192693315658275912449198365737965570035264841782399978307388920681068646219895287752359564029778568376881425070363592696751183359
    76. p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
    77. q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741
    78. e = 0x1337
    79. cp = c % p
    80. cq = c % q
    81. mp = AMM_rth(cp, e, p)
    82. mq = AMM_rth(cq, e, q)
    83. rt1 = ALL_ROOT2(e, p)
    84. rt2 = ALL_ROOT2(e, q)
    85. amp = ALL_Solution(mp, p, rt1, cp, e)
    86. amq = ALL_Solution(mq, q, rt2, cq, e)
    87. calc(amp, amq, e, p, q)

    三、基于小加密指数的CRT

        基于小加密指数的CRT,基本有以下特征。e的大小就是方程组的数目

    [0CTF 2016] rsa

    思路:

        下载附件,我们可以获取得到两个文件。其中pem可以使用openssl指令获取里面的内容。当然也可以使用其他方式例如:

    1. from Crypto.PublicKey import RSA
    2. f = open("public.pem")
    3. data = f.read()
    4. s = RSA.importKey(data)
    5. print(s.n)
    6. print(s.e)
    7. n = 23292710978670380403641273270002884747060006568046290011918413375473934024039715180540887338067
    8. e = 3
    9. f.close()
    10. f = open("flag.enc", 'rb')
    11. data = f.read()
    12. print(bytes_to_long(data))
    13. c = 2485360255306619684345131431867350432205477625621366642887752720125176463993839766742234027524

        读取完文件后,我们已知的消息有(n, e, c), 其中我们需要求解m,那么我需要知道因子才能获取得到d,进而获取得到m。

    print(n.bit_length())

    #314

        看到n的位数很小,因此我们可以分解n。

    p = 26440615366395242196516853423447

    q = 27038194053540661979045656526063

    r  = 32581479300404876772405716877547

     接下来分析数据特征

    print((p - 1) * (q - 1) * (r - 1) % e)

    print((p - 1) % e)

    print((q - 1) % e)

    print((r - 1) %  e)

        在关注到e的大小为因子的数目从模数运算角度出发拆分是一种极其重要的思维。所以我们可以通过拆分n得到足够的方程数。所以,我们需要将CRT纳入考虑范围。除此之外,我们还应该考虑到,有且仅有(q - 1)不是e的倍数,因此还要考虑有限域开根或者说是解方程。获取得到c的e根次。

    1. p = 26440615366395242196516853423447
    2. q = 27038194053540661979045656526063
    3. r = 32581479300404876772405716877547
    4. ct = 2485360255306619684345131431867350432205477625621366642887752720125176463993839766742234027524
    5. PR. = PolynomialRing(Zmod(p))
    6. f = x^3-ct
    7. res1 = f.roots()
    8. PR. = PolynomialRing(Zmod(q))
    9. f = x^3-ct
    10. res2 = f.roots()
    11. PR. = PolynomialRing(Zmod(r))
    12. f = x^3-ct
    13. res3 = f.roots()
    14. for x in res1:
    15. for y in res2:
    16. for z in res3:
    17. m = crt([int(x[0]),int(y[0]),int(z[0])],[int(p),int(q),int(r)])
    18. if b'0ctf'in long_to_bytes(m):
    19. print(long_to_bytes(m))

    四、基于小加密指数的加密体系 -- Rabin

         Rabin加密体系采用了二次剩余式。因此e = 2。于此同时Rabin体系也与有限域开根有不小的联系。下面我们给出一些二次剩余式子的特点,也是Rabin体系的关键。

    因为Rabin体系下的因子满足: (p + 1) % 4 == 0。因此我们可以使用上述特使状态下的算法。同时我们也应该看到,这里是对因子做出了拆分。因此有一个条件就是拆分后的方程数==e的大小,因此我们应该想到使用CRT

    值得一提的是,Rabin体系的迭套,也往往基于2的指数次。

    [BUUCTF 2023新生赛 WEEK4]Signin

    1. from Crypto.Util.number import isPrime,bytes_to_long, sieve_base
    2. from random import choice
    3. from secret import flag
    4. m=bytes_to_long(flag)
    5. def uniPrime(bits):
    6. while True:
    7. n = 2
    8. while n.bit_length() < bits:
    9. n *= choice(sieve_base)
    10. if isPrime(n + 1):
    11. return n + 1
    12. p=uniPrime(512)
    13. q=uniPrime(512)
    14. n=p*q
    15. e= 196608
    16. c=pow(m,e,n)
    17. print("n=",n)
    18. print("c=",c)
    19. '''
    20. n= 3326716005321175474866311915397401254111950808705576293932345690533263108414883877530294339294274914837424580618375346509555627578734883357652996005817766370804842161603027636393776079113035745495508839749006773483720698066943577445977551268093247748313691392265332970992500440422951173889419377779135952537088733
    21. c= 2709336316075650177079376244796188132561250459751152184677022745551914544884517324887652368450635995644019212878543745475885906864265559139379903049221765159852922264140740839538366147411533242116915892792672736321879694956051586399594206293685750573633107354109784921229088063124404073840557026747056910514218246
    22. '''

    思路:

        首先,我们可以很直观的判断出第一层的加密是P-1光滑攻击。因此我们可以采用以下脚本。

    1. import gmpy2
    2. def Pollard(N):
    3. a = 2
    4. n = 2
    5. while True:
    6. a = pow(a, n, N)
    7. g = gmpy2.gcd(a - 1, N)
    8. if (g != 1 and g != N):
    9. print(g)
    10. break
    11. n += 1
    12. N= 3326716005321175474866311915397401254111950808705576293932345690533263108414883877530294339294274914837424580618375346509555627578734883357652996005817766370804842161603027636393776079113035745495508839749006773483720698066943577445977551268093247748313691392265332970992500440422951173889419377779135952537088733
    13. Pollard(N)
    14. '''
    15. 输出结果:
    16. p = 11104262127139631006017377403513327506789883414594983803879501935187577746510780983414313264114974863256190649020310407750155332724309172387489473534782137699
    17. 进而获取q:
    18. q = N // p
    19. '''

        接下来,我们开始分析数据特征,首先观察到e为偶数,所以我们排除这道题的后续解密是常规的。因此,做出以下的代码分析特征

    e = 196608

    print(gmpy2.gcd((p - 1) * (q - 1), e))

    print((p - 1) * (q - 1) % e)

    print((p - 1) % e)

    print((q - 1) % e)

        因此我们可以判断我们不能使用有限域开根。因为GCD(e, phi(N)) 比较小,我们可以考虑视m**GCD(phi(N), e)为新单元。但是问题就在这。通过这个想法,我们发现这是一个不断嵌套的过程。于是,自然的查看e的分解数。

        我们发现e = 2**16 * 3。因为我们在解密过程中发现是不断嵌套的。因此我们可以2为一次解密过程。并且使用因子拆解方程,进而使用CRT。也就是Rabin体系,以下是验证环节。如果你担心不满足情况,那么可以使用解方程的形式。

     print((p + 1) % 4)

     print((q + 1) % 4)

        因此有以下解密脚本:

    1. N= 3326716005321175474866311915397401254111950808705576293932345690533263108414883877530294339294274914837424580618375346509555627578734883357652996005817766370804842161603027636393776079113035745495508839749006773483720698066943577445977551268093247748313691392265332970992500440422951173889419377779135952537088733
    2. #Pollard(N)
    3. p = 11104262127139631006017377403513327506789883414594983803879501935187577746510780983414313264114974863256190649020310407750155332724309172387489473534782137699
    4. q = N // p
    5. inv_p = primefac.modinv(p, q)
    6. inv_q = primefac.modinv(q, p)
    7. c = 2709336316075650177079376244796188132561250459751152184677022745551914544884517324887652368450635995644019212878543745475885906864265559139379903049221765159852922264140740839538366147411533242116915892792672736321879694956051586399594206293685750573633107354109784921229088063124404073840557026747056910514218246
    8. cs = [c]
    9. for i in range(16):
    10. ps = []
    11. for c in cs:
    12. r = pow(c, (p + 1) // 4, p)
    13. s = pow(c, (q + 1) // 4, q)
    14. x = (r*inv_q*q + s * inv_p * p) % N
    15. y = (r*inv_q*q - s * inv_p * p) % N
    16. if x not in ps:
    17. ps.append(x)
    18. if N - x not in ps:
    19. ps.append(N - x)
    20. if y not in ps:
    21. ps.append(y)
    22. if N - y not in ps:
    23. ps.append(N - y)
    24. cs = ps #嵌套
    25. #最后求解出来的是m**3而不是m。请看e的分解
    26. for m in ps:
    27. flag = long_to_bytes(gmpy2.iroot(m, 3)[0])
    28. print(flag)

        如果你没有验证Rabin体制下的特殊性。你可以使用解方程来反复求解出根。这里只是验证发现满足特殊情况,从而使用特殊情况算法来求解根。

  • 相关阅读:
    学了Hadoop之后,如何快速理解Spark?
    软件测试面试大家是不是一问到项目就不会了?
    Python---内置方法
    集成正态云和动态扰动的哈里斯鹰优化算法
    JDBC002--java中连接数据库后执行insert插入记录的操作
    Linux之进程创建及退出
    记一次vue3页面倒计时(定时器)切换页面问题
    Jenkins-Android源码编译【架构设计】(适用鸿蒙/自动化/多产品/持续迭代)
    Flask 学习-61.Flask-Mail 发送邮件
    图片批量编辑器,轻松拼接多张图片,创意无限!
  • 原文地址:https://blog.csdn.net/qq_73643549/article/details/134096838