Write Up - IrisCTF 2023: babynotrsa | Why you should beware tweaking RSA
Informations
Sincere acknowledgements to aaSSfxxx who helped me a bit with mathematical demonstrations and kindly read my write up before I posted it.
This challenge is one of the ones I flagged during the IrisCTF 2023. Thanks to the organizers, I didn’t tried lots challenges but that one was particulary fun as a beginner.
Challenge
Name: babynotrsa
Author: sera
Description:
Everyone knows RSA, but everyone also knows that RSA is slow. Why not just use a faster operation than exponentiation?
Files:
chal.py
from Crypto.Util.number import getStrongPrime
# We get 2 1024-bit primes
p = getStrongPrime(1024)
q = getStrongPrime(1024)
# We calculate the modulus
n = p*q
# We generate our encryption key
import secrets
e = secrets.randbelow(n)
# We take our input
flag = b"irisctf{REDACTED_REDACTED_REDACTED}"
assert len(flag) == 35
# and convert it to a number
flag = int.from_bytes(flag, byteorder='big')
# We encrypt our input
encrypted = (flag * e) % n
print(f"n: {n}")
print(f"e: {e}")
print(f"flag: {encrypted}")
output.txt
:
n: 21429933885346644587620272790089165813353259223649897308397918491861562279767580488441831451651834802520437234248670652477414296159324726172158330221397420877323921934377321483041598028053870169281419856238830264612049920637819183013812186448416408328958360799645342598727238977986741643705720539702955864527935398839069236768630867447760912744208154645904678859979378604386855741350220991958191408182147658532111413386776058224418484895056146180001830405844881486308594953615999140110712045286000170660686758188247928230655746746482354748673482506070246808187808961599576834080344066055446605664648340486804023919467
e: 10788856448030235429585145974385410619185237539198378911887172763282204686697141640582780419040340318300048024100764883750608733331571719088729202796193207904701854848679412033514037149161609202467086017862616635522167577463675349103892366486246290794304652162107619408011548841664240624935414339021041162505899467159623692906986841033101688573177710503499081107294555688550493634416552587963816327790111808356639558596438537569271043190414208204773219496030644456745185896540608008662177117212000718802474957268532153146989410300300554162811564064457762004188326986236869603714437275058878379647196886872404148116134
flag: 3954523654845598592730156937269688140867480061118457307435945875579028695730063528424973907208923014508950419982702682082417623843946231057553311028711409093751376287876799688357176816093484535703797332422565021382453879908968161161537921292725907853309522100738603080298951279637316809695591295752657105226749125868510570125512146397480808774515489938198191435285342823923715673372695893409325086032930406554421670815433958591841773705563688270739343539481283865883427560667086249616210745997056621098406247201301461721906304555526293017773805845093545204570993288514598261070097976786800172141678030841959348372097
First approach
Let be output.txt
).
Reminder: RSA cryptosystem is based on modular exponentation, and compute messages this way:
The algorithm looks almost like a classic RSA encryption.
However, unlike RSA, this algorithm uses multiplication instead of exponentation as a way to compute
This implies that the decryption is made like this:
Where
Throughout all this write up, we’ll admit that chal.py
below that the chance for them to not being coprime (if the randbelow(n)
fonction returns
# We get 2 1024-bit primes
p = getStrongPrime(1024)
q = getStrongPrime(1024)
# We calculate the modulus
n = p*q
# We generate our encryption key
import secrets
e = secrets.randbelow(n)
So, what’s the matter ?
To decrypt
First method
Let’s define two functions:
Therefore:
To satisfy this equation, we must have
Which is the same as:
We got
Second method (Bézout’s identity and extended Euclidean algorithm)
Admiting
From there, and because
This is exactly what we were searching for. Replacing
Solution
We compute
Once we’ve got
from Crypto.Util.number import *
n = 21429933885346644587620272790089165813353259223649897308397918491861562279767580488441831451651834802520437234248670652477414296159324726172158330221397420877323921934377321483041598028053870169281419856238830264612049920637819183013812186448416408328958360799645342598727238977986741643705720539702955864527935398839069236768630867447760912744208154645904678859979378604386855741350220991958191408182147658532111413386776058224418484895056146180001830405844881486308594953615999140110712045286000170660686758188247928230655746746482354748673482506070246808187808961599576834080344066055446605664648340486804023919467
e = 10788856448030235429585145974385410619185237539198378911887172763282204686697141640582780419040340318300048024100764883750608733331571719088729202796193207904701854848679412033514037149161609202467086017862616635522167577463675349103892366486246290794304652162107619408011548841664240624935414339021041162505899467159623692906986841033101688573177710503499081107294555688550493634416552587963816327790111808356639558596438537569271043190414208204773219496030644456745185896540608008662177117212000718802474957268532153146989410300300554162811564064457762004188326986236869603714437275058878379647196886872404148116134
flag = 3954523654845598592730156937269688140867480061118457307435945875579028695730063528424973907208923014508950419982702682082417623843946231057553311028711409093751376287876799688357176816093484535703797332422565021382453879908968161161537921292725907853309522100738603080298951279637316809695591295752657105226749125868510570125512146397480808774515489938198191435285342823923715673372695893409325086032930406554421670815433958591841773705563688270739343539481283865883427560667086249616210745997056621098406247201301461721906304555526293017773805845093545204570993288514598261070097976786800172141678030841959348372097
d = pow(e, -1, n)
decrypted_flag = flag * d % n
print(long_to_bytes(decrypted_flag).decode())
irisctf{discrete_divide_isn't_hard}
Bonus : small example on the extended Euclidean algorithm
The extended Euclidean algorithm allows us to not only to find the gcd of two number, but also to find Bézout coefficients
The book Introduction to Algorithms (written in part by one of the inventors of RSA; for the fun fact) gives a pseudocode recursive version of the algorithm. Here is a Python code snippet I wrote from it to fit our example :
def egcd(e, n):
if n == 0:
return e, 1, 0
gcd, u, v = egcd(n, e % n)
return gcd, v, u - (e//n)*v
e, n = 7, 33
result = egcd(e, n)
print(f"gcd({e},{n}) = {result[0]}; u = {result[2]}; v = {result[1]}")
With
But also, we notice that the definition of
I won’t explain much about this algorithm because it would weigh down the article (and I couldn’t see myself explaining it in details in this write up to be honest).
I hope you learnt something (that modifiying just a tiny part of a cryptosystem can be crucial, for example) reading this wu/article. Give me feedback on Discord (REDACTED) or my other social medias if you liked it; I would really appreciate it.