Skip to content

Commit 890dd3c

Browse files
committed
mprsa writeup
1 parent 414c64b commit 890dd3c

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed

Diff for: 2017-07-15-ctfzone/mprsa/README.md

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# MPRSA (crypto)
2+
3+
## ENG
4+
[PL](#pl-version)
5+
6+
In the task we get [RSA public key components](public.txt), [encrypted flag](data.enc) and [source code](mprsa.py) used for encryption.
7+
The code contains a classic multiprime RSA implementation, with an interesting key generation code:
8+
9+
```python
10+
def key_gen(self, bits, prime_numbers=4):
11+
delta = randint(5, 15)
12+
bit_prime = int(bits // prime_numbers)
13+
14+
P = [next_prime(number.getPrime(bit_prime) + 1)]
15+
for i in range(1, prime_numbers):
16+
P.append(next_prime(P[i - 1] * delta))
17+
18+
n = self.__compute_module(P)
19+
phi = self.__compute_phi(P)
20+
21+
for d_next in count(int(pow(P[0] // 2, 0.5)), -1):
22+
g, e, __ = gcdext(d_next, phi)
23+
if (1 < e < n) and (g == 1) and (gcd(phi, e) == 1):
24+
d = d_next
25+
break
26+
27+
self.public_key = (e, n)
28+
self.secret_key = (d, n)
29+
```
30+
31+
The initiall issue we noticed with this code, is that modulus `n` is composed of strongly non-random primes, since `.next_prime` is deterministic and `delta` multiplier is very small.
32+
We've seen some writeups for this task which attacked this vector.
33+
34+
Nevertheless, we have a bit of experience already with attacks on RSA and we've noticed one other interesting fact here:
35+
36+
```python
37+
for d_next in count(int(pow(P[0] // 2, 0.5)), -1):
38+
```
39+
40+
Private key exponents starts from `sqrt(p1/2)` and we know that p1 is the smallest of the primes in `n` and they are of similar size, therefore `p1` is smaller than `n^(1/4)`.
41+
42+
And we know that if `d < 1/3 n^(1/4)` then we can use Wiener's attack to recover the private exponent.
43+
It's clear that we are lucky and we can recover the key with a bit of sage:
44+
45+
```python
46+
e = 2968282037100353640375137899109790499983904510372252123726372200136866453960017151334469454219618530252326391316368089337062513360207381202191915473462935477137523455963250056561696664667826520897145326882242932509636924316993816382503962649302107865422204292490659961123103322081852240437978613121365781016988448211321349469941008479597808471102164820173139919110860676464533506147455712945961147297193425603466185665772219928497258618754492859488589873906043003885893571962433509510568617898956135134801893952671289895841202079907023382879176353447845431980339763701845065932967492613174149948295178658632744337984598033199716909609691917091599333032421515584590767434316739374277008976624091929263313294017958203501962609986428734553144207841375915976037349385525685765751825435583700725710652618107250634373424713513298201017768173878869803169781015337283490319756398578109078482368725206020186761161884650413182297877151106135232838271785994275915310662858329477083914589917431343036266926436535406078268574331773960697696088892795445640924833807153106889785640164637689271399503064510417142492169690916011945805675154490404590528925067599406358567902459063109040410209462273031696409389388590120586013927889551821936657759836121166591
47+
n = 7514486184413883943206134802309178399244378977612173666918494750761691891054947551148635071227769468578429057411933207521812645312852372491525360936618326543031520002708891330196401800722400435500157085990690437665009726219084442021182850506847121543952655588437818213790488615953323918596261471907835421407596459273791581399309405067626383928217548743866594178747621345881632069955681378662964970779524097614470204109881600043967504127490912520547758072473768719527077924134830122844355992675524808082077564650441063165395654489609498673176326527753016138066814814395200582603579511246113422000711435941608107654792503944786693356696589418688102700165482722623897706829970814110646089600275631212777003792683291735426294012686607809533096193939103941428766195023630255837719510277444701463006437791991196936648896229397094403915485049521731674097516242423233615004601202795680477677383876821794953563585797462940468885019612996080647173400509657498552114237186425176692867162493697752241051962151120715653607272964311445754089586884116532125369172407750688737448422035240971409748803419916890500367552066268915926436633178471526464741419410486387714614840372951024874043659727111073041432865136565615528171567027369016567760790667844170057
48+
49+
c_fracs = continued_fraction(e/n).convergents()
50+
test_message = 42
51+
test_message_encrypted = pow(test_message,e,n)
52+
d = 0
53+
for i in xrange(len(c_fracs)):
54+
if pow(test_message_encrypted,c_fracs[i].denom(),n) == test_message:
55+
d = c_fracs[i].denom()
56+
break
57+
print(d)
58+
```
59+
60+
And with `d` we can simply decode the flag:
61+
62+
```python
63+
from crypto_commons.generic import long_to_bytes
64+
65+
66+
def main():
67+
n = 7514486184413883943206134802309178399244378977612173666918494750761691891054947551148635071227769468578429057411933207521812645312852372491525360936618326543031520002708891330196401800722400435500157085990690437665009726219084442021182850506847121543952655588437818213790488615953323918596261471907835421407596459273791581399309405067626383928217548743866594178747621345881632069955681378662964970779524097614470204109881600043967504127490912520547758072473768719527077924134830122844355992675524808082077564650441063165395654489609498673176326527753016138066814814395200582603579511246113422000711435941608107654792503944786693356696589418688102700165482722623897706829970814110646089600275631212777003792683291735426294012686607809533096193939103941428766195023630255837719510277444701463006437791991196936648896229397094403915485049521731674097516242423233615004601202795680477677383876821794953563585797462940468885019612996080647173400509657498552114237186425176692867162493697752241051962151120715653607272964311445754089586884116532125369172407750688737448422035240971409748803419916890500367552066268915926436633178471526464741419410486387714614840372951024874043659727111073041432865136565615528171567027369016567760790667844170057
68+
d = 9427062506559859200764441560060897853452091503537282553799991491531587159716894888858396729480853980609608783434755632459538177527336880678476984732352511
69+
ct = 4990981759460304744105598767593686181405870005282225829795794541021226151966053079510943795109726609634828370167775307839662644021918767556530119412853816585221569546843939870445288438295880322602517246037112564416212745954141726471664361647045729235670622890953655065235230427298013906810014221648290750692583336186843003229107021202513937560627163229698907224982160099413064560450430189221548918249561722797270239205285019947483419790983776163671611001827036804081081707549809205146146016914228431689911951835061650007130105435596899572248580145216361550470379538250892374083206633208114199207657470199269462010122511529769658733474277302308656490658251694852119519651331026206905848184310474442594518003923697214854504891077728222935182875777284193900483103844390422979429620136337089544700764854729601666550485708645758202313582038929079609869996469534041940940326632417337431671554125949585769777514656385405640728690453834779703498214246941789126527089991023766694976273980553865664242840580534044580685023115108182135139502041838131616984809782973256326815445038141870218251128685050551152554710812132312358766591390023888015234480632150114384947814031965110524912964541892010650475016456100706107619225121444952046171313017830946278
70+
print(long_to_bytes(pow(ct, d, n)))
71+
main()
72+
```
73+
74+
And we get:
75+
76+
```
77+
Mr.D (12:10):
78+
Okey, see you later ;)
79+
80+
Mr.D (19:30):
81+
So can you help me?
82+
83+
Anonymous (19:31):
84+
Yeah, we will have 10,000 falsified voters. Transfer 100000$ to my bank account: ctfzone{3177809746931830}
85+
```
86+
87+
## PL version
88+
89+
W zadaniu dostajemy [klucz publiczny RSA](public.txt), [zaszyfrowaną flagę](data.enc) oraz [kod szyfrowania](mprsa.py) wykorzystany do szyfrowania danych.
90+
Kod zawira klasyczną implementacje RSA opartego o wiele liczb pierwszych, z dość ciekawą logiką generacji klucza:
91+
92+
```python
93+
def key_gen(self, bits, prime_numbers=4):
94+
delta = randint(5, 15)
95+
bit_prime = int(bits // prime_numbers)
96+
97+
P = [next_prime(number.getPrime(bit_prime) + 1)]
98+
for i in range(1, prime_numbers):
99+
P.append(next_prime(P[i - 1] * delta))
100+
101+
n = self.__compute_module(P)
102+
phi = self.__compute_phi(P)
103+
104+
for d_next in count(int(pow(P[0] // 2, 0.5)), -1):
105+
g, e, __ = gcdext(d_next, phi)
106+
if (1 < e < n) and (g == 1) and (gcd(phi, e) == 1):
107+
d = d_next
108+
break
109+
110+
self.public_key = (e, n)
111+
self.secret_key = (d, n)
112+
```
113+
114+
Pierwszą podatnością, którą zauważyliśmy w tym kodzie był sposób wyliczania modulusa `n`, który składa się z mocno nie-losowych liczb pierwszych, bo `.next_prime` jest deterministyczne a `delta` jest dość niewielka.
115+
Widzieliśmy writeupy które atakowały zadanie zgodnie z tym wektorem.
116+
117+
Niemniej mamy już trochę doświadczenia z atakami na RSA i zauważyliśmy inną ciekawostkę w kodzie:
118+
119+
```python
120+
for d_next in count(int(pow(P[0] // 2, 0.5)), -1):
121+
```
122+
123+
Prywatny wykładnik szyfrujący zaczyna się od `sqrt(p1/2)` a wiemy że p1 jest najmniejszym czynnikiem pierwszym w `n` i że czynniki są zbliżonego rozmiaru, więc `p1` musi być mniejsze od `n^(1/4)`.
124+
125+
Wiemy też że jeśli `d < 1/3 n^(1/4)` to możemy użyć ataku Wienera aby odzyskać prywatny wykładnik szyfrujący.
126+
Jak widać mamy szczęście i możemy użyc prostego skryptu sage:
127+
128+
```python
129+
e = 2968282037100353640375137899109790499983904510372252123726372200136866453960017151334469454219618530252326391316368089337062513360207381202191915473462935477137523455963250056561696664667826520897145326882242932509636924316993816382503962649302107865422204292490659961123103322081852240437978613121365781016988448211321349469941008479597808471102164820173139919110860676464533506147455712945961147297193425603466185665772219928497258618754492859488589873906043003885893571962433509510568617898956135134801893952671289895841202079907023382879176353447845431980339763701845065932967492613174149948295178658632744337984598033199716909609691917091599333032421515584590767434316739374277008976624091929263313294017958203501962609986428734553144207841375915976037349385525685765751825435583700725710652618107250634373424713513298201017768173878869803169781015337283490319756398578109078482368725206020186761161884650413182297877151106135232838271785994275915310662858329477083914589917431343036266926436535406078268574331773960697696088892795445640924833807153106889785640164637689271399503064510417142492169690916011945805675154490404590528925067599406358567902459063109040410209462273031696409389388590120586013927889551821936657759836121166591
130+
n = 7514486184413883943206134802309178399244378977612173666918494750761691891054947551148635071227769468578429057411933207521812645312852372491525360936618326543031520002708891330196401800722400435500157085990690437665009726219084442021182850506847121543952655588437818213790488615953323918596261471907835421407596459273791581399309405067626383928217548743866594178747621345881632069955681378662964970779524097614470204109881600043967504127490912520547758072473768719527077924134830122844355992675524808082077564650441063165395654489609498673176326527753016138066814814395200582603579511246113422000711435941608107654792503944786693356696589418688102700165482722623897706829970814110646089600275631212777003792683291735426294012686607809533096193939103941428766195023630255837719510277444701463006437791991196936648896229397094403915485049521731674097516242423233615004601202795680477677383876821794953563585797462940468885019612996080647173400509657498552114237186425176692867162493697752241051962151120715653607272964311445754089586884116532125369172407750688737448422035240971409748803419916890500367552066268915926436633178471526464741419410486387714614840372951024874043659727111073041432865136565615528171567027369016567760790667844170057
131+
132+
c_fracs = continued_fraction(e/n).convergents()
133+
test_message = 42
134+
test_message_encrypted = pow(test_message,e,n)
135+
d = 0
136+
for i in xrange(len(c_fracs)):
137+
if pow(test_message_encrypted,c_fracs[i].denom(),n) == test_message:
138+
d = c_fracs[i].denom()
139+
break
140+
print(d)
141+
```
142+
143+
I teraz mając już `d` możemy odszyfrować dane:
144+
145+
```python
146+
from crypto_commons.generic import long_to_bytes
147+
148+
149+
def main():
150+
n = 7514486184413883943206134802309178399244378977612173666918494750761691891054947551148635071227769468578429057411933207521812645312852372491525360936618326543031520002708891330196401800722400435500157085990690437665009726219084442021182850506847121543952655588437818213790488615953323918596261471907835421407596459273791581399309405067626383928217548743866594178747621345881632069955681378662964970779524097614470204109881600043967504127490912520547758072473768719527077924134830122844355992675524808082077564650441063165395654489609498673176326527753016138066814814395200582603579511246113422000711435941608107654792503944786693356696589418688102700165482722623897706829970814110646089600275631212777003792683291735426294012686607809533096193939103941428766195023630255837719510277444701463006437791991196936648896229397094403915485049521731674097516242423233615004601202795680477677383876821794953563585797462940468885019612996080647173400509657498552114237186425176692867162493697752241051962151120715653607272964311445754089586884116532125369172407750688737448422035240971409748803419916890500367552066268915926436633178471526464741419410486387714614840372951024874043659727111073041432865136565615528171567027369016567760790667844170057
151+
d = 9427062506559859200764441560060897853452091503537282553799991491531587159716894888858396729480853980609608783434755632459538177527336880678476984732352511
152+
ct = 4990981759460304744105598767593686181405870005282225829795794541021226151966053079510943795109726609634828370167775307839662644021918767556530119412853816585221569546843939870445288438295880322602517246037112564416212745954141726471664361647045729235670622890953655065235230427298013906810014221648290750692583336186843003229107021202513937560627163229698907224982160099413064560450430189221548918249561722797270239205285019947483419790983776163671611001827036804081081707549809205146146016914228431689911951835061650007130105435596899572248580145216361550470379538250892374083206633208114199207657470199269462010122511529769658733474277302308656490658251694852119519651331026206905848184310474442594518003923697214854504891077728222935182875777284193900483103844390422979429620136337089544700764854729601666550485708645758202313582038929079609869996469534041940940326632417337431671554125949585769777514656385405640728690453834779703498214246941789126527089991023766694976273980553865664242840580534044580685023115108182135139502041838131616984809782973256326815445038141870218251128685050551152554710812132312358766591390023888015234480632150114384947814031965110524912964541892010650475016456100706107619225121444952046171313017830946278
153+
print(long_to_bytes(pow(ct, d, n)))
154+
main()
155+
```
156+
157+
I dostajemy:
158+
159+
```
160+
Mr.D (12:10):
161+
Okey, see you later ;)
162+
163+
Mr.D (19:30):
164+
So can you help me?
165+
166+
Anonymous (19:31):
167+
Yeah, we will have 10,000 falsified voters. Transfer 100000$ to my bank account: ctfzone{3177809746931830}
168+
```

Diff for: 2017-07-15-ctfzone/mprsa/data.enc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4990981759460304744105598767593686181405870005282225829795794541021226151966053079510943795109726609634828370167775307839662644021918767556530119412853816585221569546843939870445288438295880322602517246037112564416212745954141726471664361647045729235670622890953655065235230427298013906810014221648290750692583336186843003229107021202513937560627163229698907224982160099413064560450430189221548918249561722797270239205285019947483419790983776163671611001827036804081081707549809205146146016914228431689911951835061650007130105435596899572248580145216361550470379538250892374083206633208114199207657470199269462010122511529769658733474277302308656490658251694852119519651331026206905848184310474442594518003923697214854504891077728222935182875777284193900483103844390422979429620136337089544700764854729601666550485708645758202313582038929079609869996469534041940940326632417337431671554125949585769777514656385405640728690453834779703498214246941789126527089991023766694976273980553865664242840580534044580685023115108182135139502041838131616984809782973256326815445038141870218251128685050551152554710812132312358766591390023888015234480632150114384947814031965110524912964541892010650475016456100706107619225121444952046171313017830946278

Diff for: 2017-07-15-ctfzone/mprsa/mprsa.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
from Crypto.Util import number
3+
from binascii import hexlify, unhexlify
4+
from gmpy2 import next_prime, powmod, gcdext, gcd
5+
from itertools import count
6+
from random import randint
7+
8+
9+
class MPRSA(object):
10+
def __init__(self):
11+
self.public_key = None
12+
self.secret_key = None
13+
14+
def key_gen(self, bits, prime_numbers=4):
15+
delta = randint(5, 15)
16+
bit_prime = int(bits // prime_numbers)
17+
18+
P = [next_prime(number.getPrime(bit_prime) + 1)]
19+
for i in range(1, prime_numbers):
20+
P.append(next_prime(P[i - 1] * delta))
21+
22+
n = self.__compute_module(P)
23+
phi = self.__compute_phi(P)
24+
25+
for d_next in count(int(pow(P[0] // 2, 0.5)), -1):
26+
g, e, __ = gcdext(d_next, phi)
27+
if (1 < e < n) and (g == 1) and (gcd(phi, e) == 1):
28+
d = d_next
29+
break
30+
31+
self.public_key = (e, n)
32+
self.secret_key = (d, n)
33+
34+
def import_keys(self, public_key, secret_key):
35+
self.public_key = public_key
36+
self.secret_key = secret_key
37+
38+
def export_keys(self):
39+
return self.public_key, self.secret_key
40+
41+
@staticmethod
42+
def __compute_module(primes):
43+
n = 1
44+
for prime in primes:
45+
n *= prime
46+
return n
47+
48+
@staticmethod
49+
def __compute_phi(primes):
50+
phi = 1
51+
for prime in primes:
52+
phi *= (prime - 1)
53+
return phi
54+
55+
@staticmethod
56+
def __encode_message(data):
57+
return int(hexlify(data), 16)
58+
59+
@staticmethod
60+
def __decode_message(data):
61+
return unhexlify(format(data, "x"))
62+
63+
def encryption(self, ptext):
64+
data = self.__encode_message(ptext)
65+
return powmod(data, self.public_key[0], self.public_key[1])
66+
67+
def decryption(self, ctext):
68+
data = powmod(ctext, self.secret_key[0], self.secret_key[1])
69+
return MPRSA.__decode_message(data)

0 commit comments

Comments
 (0)