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?}

Related Posts

Cyber Sea Game 2017: rsa-8192 (200)
MeePwn CTF 2017: |\/|/-\T|-| (100)
TUCTF 2017: Crypto Clock (300)
Cyber Sea Game 2017: addcrypto (50)