MeePwn CTF 2017: Simpler RSA (100)
We are given two files: one is simple.py, which used to do an encryption, and pubkey.txt,
which is the generated public key when encrypting with simple.py. Here's the content of
simpe.py.
from Crypto.Util.number import *
import random
# from flag import FLAG
def generate(nbits):
p = getPrime(nbits)
q = getPrime(nbits)
n = p * q * p
g = random.randint(1, n)
h = pow(g, n, n)
return (n, g, h)
def encrypt(m, n, g, h):
r = random.randint(1, n)
c = pow(pow(g, m, n) * pow(h, r, n), 1, n)
return c
m = [ord(char) for char in FLAG]
n, g, h = generate(90)
open("pubkey.txt", "w").write("{0}:{1}:{2}".format(n, g, h))
c = [encrypt(mi, n, g, h) for mi in m]
open("enc.txt", "w").write(str(c))
From this code, we can see that the value of n will be about 270 bits, which is quite small to
be factorized. Let's try factorize n.
The best factorization algorithm that I know of is quadratic
sieve, which apparently could
factorize 270 bits integer quite fast. Looking for good implementation
quadratic sieve, I came across msieve. The
README says it is appropriate for integers with less than 100 digits. Running
msieve yield p = 1057817919251064684989791981 and q =
1103935256393984899021164397.
To decrypt each number in c, we need find m which is passed to encrypt function. Since m
will be in [1-255], this can be done by brute-forcing m and then validate if it is the right
byte. But, how can we validate m?
Doing pow(x,1,n) basically means \(x \pmod n\), so we have two terms for the
encryption, pow(g, m, n) and pow(h, r, n). We can cancel out pow(g, m, n)
by multiplying c with inverse modulo \(g^{m} \pmod n\). This leave us with
pow(h, r, n). At initialization, h = pow(g, n, n) means pow(h, r, n) is
the same as pow(g, n*r, n). Now, to cancel this term, we need to multiply it
with inverse modulo of \(g^{nr}\). But the r is chosen randomly when
encrypting, so we don't know the value of \(g^{nr}\).
To cancel \(g^{nr}\), we don't need to find the inverse modulo, we can instead find \(x\) such that \(g^{xnr} \equiv 1 \pmod n\). From Euler's theorem, we know that if \((xnr) \equiv 0 \pmod{\phi{( n )}}\), then \(g^{xnr} \equiv 1 \pmod n\). Thus, \(x\) is \(\phi{( n )}/gcd(\phi{(n)}, n)\).
If we pick the wrong m (let's say guessed m is \(m'\) and the actual m is \(m\)), this leave
the term \(g^{m'-m}\), which will not be equal to zero.
Here's the Python script to decrypt the ciphers:
import random
# public keys
n = 1235280093599323856390922798440377476467763531842392869674688408727824382702235317
g = 1110549711091392805024587195974719739929628997819528005374351081843256209971586072
h = 610466084395822279908554174354632326166097007218620288020807622478449585661028278
assert (pow(g,n,n) == h)
# primes of n
p = 1057817919251064684989791981
q = 1103935256393984899021164397
assert(p*p*q == n)
def gcd(a,b):
if b == 0: return a
return gcd(b, a % b)
totient = (p*(p-1)*(q-1))/gcd(p*(p-1),q-1)
cf = gcd(n,totient)
x = totient/cf
def invmod(a):
return pow(a,totient-1,n)
for i in range(2, 200):
assert( gcd(i, n) == 1 )
assert( (i*invmod(i)) % n == 1 )
cipher = [1042684026433440609442082085056733059350277230855631475910449335289565957026925888L, 337451979363258259789969117391527857381178461709773977486124377788140579446959643L, 955044246855117318302069848746627916980637349888954523552188603828273408620783185L, 565084528151941455613387670829541401837686258558304725363697970401158298304784226L, 1217257199494998716855441753878986048959547533911751395319106679987808998530722005L, 500431401136979664818320806505265313829298163109190333815615071157569573408761776L, 918364738570517634370611128928418073524271175997413722821007998543195365584145047L, 1231818404429995719463458343454764290314747233553619629902141039984717307144020885L, 578663363706556074852662555903558050626510818844939214634690712022753850484044077L, 170573141141793020278685561667014942396340687197982787584671361315770701412232820L, 525046404028002961161648698133126471909762913783905233407759137018767422695415644L, 548918541683229869313458652944252600295112072416629330975730507209045241266103104L, 1097888538672145356012505481951969976746432194049237781078685846091302406138295450L, 467055824810727038529002950991149449402485370247343520114443047715384113252663243L, 379325228947504950566222318955652420189554769276755237788381017120582088704791796L, 808135517143354496893964869359259307455178022544381266003759602027195395780858971L, 232206696206622845104449934990777760916638497611027428059484100417419468488368534L, 611231502156630905749534239244947175924425232566020566839692916151929122262556160L, 266317680779170923895607136980535234290396471711461778811550899033797067803838490L, 458442169696983024890517742499158555610082173984048562536247204694360339657234325L, 173156860778775111311074302992389403207838028758953619505920477407616091727957249L, 327701451317276442403593523928518039719286029867533592333080575014882858204660392L, 1061889651976594791626195662010361995350291121541576461530446716234559629390059206L, 501107468516571472868902583880130438298825499145194993732906902954639206415408661L, 793462123972295663612808938124574874398657924694272939619742740367249265250881187L, 419580729971386599858472445685002497860174205402938481254320715563201540618526181L, 1095670842511297949652166375966107611580541850315423104099581710112461928501516645L, 1207404101728741308518114885496775915613418535791624006547396431596035842097919854L, 63257756791404477719948396035346460877053804738003821615924989712602434088623116L, 709743404772556668186521811311910146690879578711491970215935941312652194503904278L, 472145667401895260338686064243470672627707713552548730414485106260542589178719305L, 671679182553553455773258809387401495029229971280677124071355178172582087439182089L, 795683594959663064302728712706823881033923895027131034114737749301583410828859935L, 788511687111620345727088836755516496428115065618134997006646145097528302404655271L, 493455498376449391174132961453557751413575238351804592824587799692504725075730030L, 1071336432168555760392522471606603558083343440707490972056560901765084761996537479L, 76414279730707452882500835328589558586805800097281864786868179845530554318636818L, 1026789233929477564953495880177610952920067780854127113113934470636602509593246257L, 1220456099417710433363957577456670914480321272794999438920511570648408214807875401L, 831001855794529333359541390744984608592774868529241373516540331083225538929223854L, 406700540669900202178972508513521627652422698239818055618399192500676530118703988L, 535793870780869641948563592689023912632863260901941937211057060307409713921739965L, 908165522898293977370960880692560887898587015153655232763363739940417879927826149L, 916463042698051909403902218425035797085521743060997736870228165259059940402603712L, 1124064575055728289901493663250187815236366261544380516480084011806758657972869579L, 302544873761485689612490183241038411927316560916804219683347601892406383914571353L, 326809752614724517585542018284901481393260541798889339649409227233748588383860516L, 828292723696438834080952146252731883891295126479141546958923438287859173086057154L, 835877660712133206131980242990809426528248997858916518574414436451562226433887025L, 145587585798997332370407645289128453608803108424500991984349925153212212859373727L, 463948250139563696175107900326108271430940645044009515704115508942300583165355378L, 944242052057563352409261236875599145504153562928055236566287627433096232314943101L, 579193856270128178962709647899702648998261560149392089962175236519890756116448359L, 1167987057311285845643231967769257342900085143163437221731184706561190379437889873L]
ans = []
for c in cipher:
for m in range(1,256):
t = c * invmod(pow(g, m, n))
if (pow(t, x, n) == 1):
ans.append(chr(m))
print(''.join(ans))
Flag is: MeePwnCTF{well_is_fact0rizati0n_0nly_w4y_to_s0lve_it?}