|
| 1 | +# Happy (crypto, 242p, 36 solved) |
| 2 | + |
| 3 | +In the challenge we get [encrypted flag](flag.enc), [rsa public key](pub.key) and [encryption code](happy). |
| 4 | +The code performs generation of RSA-CRT key (although in a bit convoluted way, to make sure there are safe primes used). |
| 5 | +Then encryption of the flag is performed using RSA with OAEP SHA1 padding. |
| 6 | +Unusually the modulus `N` is here `p*q**k`, but on its own it doesn't yet cause any issues. |
| 7 | +From the modulus size of 2300 bits, and minimum bitsize for primes set for 700, we can deduce that `p` and `q` are about 765 bits long and `k=2`. |
| 8 | + |
| 9 | +The key to solve the problem is to notice this: |
| 10 | + |
| 11 | +```ruby |
| 12 | +cf = p.pow(q ** (k - 1) * (q - 1) - 1, q ** k) |
| 13 | +``` |
| 14 | + |
| 15 | +and then |
| 16 | + |
| 17 | +```ruby |
| 18 | +def public_key |
| 19 | + Key.new(@attr.reject{|k, v| [:p, :q, :d1, :d2, :ce].include?(k)}) |
| 20 | +end |
| 21 | +``` |
| 22 | + |
| 23 | +The `cf` parameter is what is normally known as `qInv`, and in our case `cf = modinv(p,q^2)`. |
| 24 | +But if you look closely at the public key construction, the omitted parameters are `:p, :q, :d1, :d2, :ce`. |
| 25 | +There is no parameter `ce` but there was `cf`! |
| 26 | + |
| 27 | +This means that actually the value of `cf` is still stored in the public key, alongside `e` and `N`. |
| 28 | + |
| 29 | +In conclusion, we know a small part of RSA-CRT private key -> we know value `qInv` such that `qInv*p == 1 mod q**2` |
| 30 | + |
| 31 | +What we would like to calculate is `p` (or `q`). |
| 32 | +Let's rephrase that into an equation: |
| 33 | + |
| 34 | +`f(x) = qInv*x - 1 == 0 mod q**2` |
| 35 | + |
| 36 | +We want to solve such equation, because non-trivial root of this polynomial has to be `p`. |
| 37 | +Once it's written like this, it's pretty clear that it look a lot like polynomial for Coppersmith theorem. |
| 38 | +Just to recap: |
| 39 | + |
| 40 | +Given polynomial `f(x)` such that `f(x) == 0 mod N` there is a polynomial algorithm to calculate `small roots` of such polynomial mod some factor of `N` bigger than `N^beta` (where beta can be anything `0<beta<1`). |
| 41 | +Small in this case mean that for polynomial of degree `d` they have to be smaller than `N^(beta^2)/d`. |
| 42 | + |
| 43 | +In our case `p` is of similar order to `q`, so about `N^1/3` and degree of the polynomial is `1`. |
| 44 | +We know that `q^2` is about `N^2/3` and is a factor of `N`, so we can look for roots modulo `N^2/3` thus `beta = 0.6`. |
| 45 | +And with this constraint we should be able to find roots smaller than `N^(4/9)`, and we know `p` is about `N^1/3` so `N^3/9`, so it's smaller than the bound, and we should be able to find it. |
| 46 | + |
| 47 | +We proceed with the code: |
| 48 | + |
| 49 | +```python |
| 50 | +def main(): |
| 51 | + e = 65537 |
| 52 | + N = 5452318773620154613572502669913080727339917760196646730652258556145398937256752632887555812737783373177353194432136071770417979324393263857781686277601413222025718171529583036919918011865659343346014570936822522629937049429335236497295742667600448744568785484756006127827416640477334307947919462834229613581880109765730148235236895292544500644206990455843770003104212381715712438639535055758354549980537386992998458659247267900481624843632733660905364361623292713318244751154245275273626636275353542053068704371642619745495065026372136566314951936609049754720223393857083115230045986813313700617859091898623345607326632849260775745046701800076472162843326078037832455202509171395600120638911 |
| 53 | + qinv = 25895436290109491245101531425889639027975222438101136560069483392652360882638128551753089068088836092997653443539010850513513345731351755050869585867372758989503310550889044437562615852831901962404615732967948739458458871809980240507942550191679140865230350818204637158480970417486015745968144497190368319745738055768539323638032585508830680271618024843807412695197298088154193030964621282487334463994562290990124211491040392961841681386221639304429670174693151 |
| 54 | + |
| 55 | + P.<x> = PolynomialRing(Zmod(N), implementation='NTL') |
| 56 | + pol = x*qinv - 1 |
| 57 | + pol = pol.monic() |
| 58 | + roots = pol.small_roots(X=2**765, beta=0.6) |
| 59 | + print("Potential solutions:") |
| 60 | + for p in roots: |
| 61 | + q = isqrt(int(N)/int(p)) |
| 62 | + phi = (p-1)*(q-1)*q |
| 63 | + d = inverse_mod(e, phi) |
| 64 | + print(d) |
| 65 | + |
| 66 | +main() |
| 67 | +``` |
| 68 | + |
| 69 | +And almost immediately we get back `d = 313643312579885910144930879740792443079046797319702735470940304815114423813387207622962378717692956907985131193206173468032955155911357015790117906931310982300638685119345225585365379933984401550490180088069653940748930777249398681018529181837718088338410634951815720591986027326920386342449211862769317826747179543111987382083071211027548820393280953703100868439675930431579069835763288141197755585262721361909904809472100941962440764955942607730932895387899109482973057485978370088173899965076238641801547197089631820258212320243267074873219925727866388979716767504927253295557727184298435208619716766017226260721754210366993951047440280419099305076176216010566878368093440228299300269753` |
| 70 | + |
| 71 | +Now we only need to decrypt the flag. |
| 72 | +It's a bit tricky, since unlike in most CTFs, it's not textbook RSA but OAEP. |
| 73 | + |
| 74 | +To decrypt the flag we need: |
| 75 | + |
| 76 | +```python |
| 77 | +from Crypto.PublicKey import RSA |
| 78 | +from cryptography.hazmat.backends import default_backend |
| 79 | +from cryptography.hazmat.primitives import hashes, serialization |
| 80 | +from cryptography.hazmat.primitives.asymmetric import padding |
| 81 | + |
| 82 | + |
| 83 | +def main(): |
| 84 | + e = 65537L |
| 85 | + N = 5452318773620154613572502669913080727339917760196646730652258556145398937256752632887555812737783373177353194432136071770417979324393263857781686277601413222025718171529583036919918011865659343346014570936822522629937049429335236497295742667600448744568785484756006127827416640477334307947919462834229613581880109765730148235236895292544500644206990455843770003104212381715712438639535055758354549980537386992998458659247267900481624843632733660905364361623292713318244751154245275273626636275353542053068704371642619745495065026372136566314951936609049754720223393857083115230045986813313700617859091898623345607326632849260775745046701800076472162843326078037832455202509171395600120638911 |
| 86 | + d = 313643312579885910144930879740792443079046797319702735470940304815114423813387207622962378717692956907985131193206173468032955155911357015790117906931310982300638685119345225585365379933984401550490180088069653940748930777249398681018529181837718088338410634951815720591986027326920386342449211862769317826747179543111987382083071211027548820393280953703100868439675930431579069835763288141197755585262721361909904809472100941962440764955942607730932895387899109482973057485978370088173899965076238641801547197089631820258212320243267074873219925727866388979716767504927253295557727184298435208619716766017226260721754210366993951047440280419099305076176216010566878368093440228299300269753 |
| 87 | + key = RSA.construct((N, e, d)) |
| 88 | + pemkey = key.exportKey() |
| 89 | + encrypted = open("flag.enc", 'rb').read() |
| 90 | + private_key = serialization.load_pem_private_key( |
| 91 | + pemkey, |
| 92 | + password=None, |
| 93 | + backend=default_backend() |
| 94 | + ) |
| 95 | + original_message = private_key.decrypt( |
| 96 | + encrypted, |
| 97 | + padding.OAEP( |
| 98 | + mgf=padding.MGF1(algorithm=hashes.SHA1()), |
| 99 | + algorithm=hashes.SHA1(), |
| 100 | + label=None |
| 101 | + ) |
| 102 | + ) |
| 103 | + print(original_message) |
| 104 | + |
| 105 | + |
| 106 | +main() |
| 107 | +``` |
| 108 | + |
| 109 | +Which finally gives `TWCTF{I_m_not_sad__I_m_happy_always}` |
0 commit comments