“A man is crazy who writes a secret in any other way than one which will conceal it from the vulgar.” — Epistle on the Secret Works of Art and the Nullity of Magic, Roger Bacon
Fun With RSA
WOWOWOWOW what’s rsa you might ask? I’m glad you asked. here’s a little bit about our sponsor.
Rivest–Shamir–Adleman (RSA) Encryption
All positive integers can be written as the product of one or more prime factors.
Notice that for a large integer , it’s relatively difficult to decompose them into prime factors (i.e. no polynomial time algorithm currently exists, best one is sub-exponential)
Yet, it’s relatively easy to confirm that those prime factors are in fact the factors of that integer; just multiply any given prime factors and check if their product is equal to .
A process being easy one way and hard the other is an essential component of good cryptography. RSA encryption utilizes this property of prime factorization to encrypt and decrypt securely.
Setup
Step 1:
Choose distinct prime integers and that multiply to
For example, let’s say and so
Step 2:
Compute the Euler Totient function of N.
For our numbers, .
Step 3:
Choose between 1 and where is coprime with . is called the public exponent in (N,e) , since it’s used by anyone to encrypt a message to send to a person.
Let’s say in our case is 5.
Step 4:
Calculate , the multiplicative inverse of . Via the Extended Euclidean algorithm, our . It’s easier if we make it positive by adding (we use because adding doesn’t change the value mod as it’s divided out) so we don’t exponentiate by a negative number when decrypting. All in all, we get . is called the private key in (N, d), since it’s only known by the receiver of an encrypted message and used to decrypt. This is also why RSA is asymmetric encryption since two different keys are used for encryption and decryption.
Encryption
Convert a message into a number. Bits represent characters and are numbers themselves, so we can use the bytes for each character as numbers to encrypt. If ends up being bigger than (which is not good since the message would get messed up through being taken mod ), is divided into smaller portions. These are typically padded randomly to prevent RSA from becoming simple substitution and thus vulnerable to attacks on substitution ciphers.
Actually encrypting requires exponentiating it by , which is our case is 5, and then taking the result mod . Since we have such a small , we’ll need to split byte by byte. For the message “Hello, world!”, the bytes are
01001000 01100101 01101100 01101100 01101111 00101100 00100000 01110111 01101111 01110010 01101100 01100100 00100001
Converting each byte to decimal
72 101 108 108 111 44 32 119 111 114 108 100 33
and exponentiating each by produces
1934917632 10510100501 14693280768 14693280768 16850581551 164916224 33554432 23863536599 16850581551 19254145824 14693280768 10000000000 39135393
and finally mod gives
914 1183 337 337 213 347 698 807 213 456 337 693 827
Decryption
To decrypt, we exponentiate the ciphertext(s) by then take it mod , which gives us the original message. This is left as an exercise to the reader to verify, and another helpful exercise is just picking your own set up for RSA and attempting to use it to encrypt and decrypt!
The Challenges
Mind Your Ps and Qs
Decrypt my super sick RSA:
c: 717608817902276639011922897702552744324264244069272484013719744328086856277690292
n: 1368992139315019955067447961537607041364451719382349005056752379702694554965203011
e: 65537
Remember that is comprised of and , which is needed to calculate and thus the private exponent required to decrypt. Looking at the problem statement, it appears that they’re saying is small, insinuating that can be easily factored. Put it into factordb.com and they’re right! Our factors are
p = 2074606527507983503944226222695401999839
q = 659880377875535146491994798968813949893149
Thus our is
1368992139315019955067447961537607041363789764397945961926756440677503045613310024
Calculating such that , we then get
-33463927356800921128798261049227863348410687131933250393009503302948866732876431
where we can add to end up at
1335528211958219033938649700488379178015379077266012711533746937374554178880433593
We raise the ciphertext to the power of and get
print(pow(717608817902276639011922897702552744324264244069272484013719744328086856277690292, 1335528211958219033938649700488379178015379077266012711533746937374554178880433593, 1368992139315019955067447961537607041364451719382349005056752379702694554965203011))
# outputs 310924469227196415248432899983304451945245691282380935847926787838996952580711265
Convert to binary
101101000011011101100011000100100000110110100000000000100110111110010110110111010000110110111001000010100111110111100100110001010011110100110101010010010101001011101111010011000001001110000100001111011010011101111110000100110111111100110010000101100100100110110110111001
and the converting each byte to text, we get
§Ó#Sv66öFóuóåôåó×7·ÖVF6
which… doesn’t look quite right.
Why?
Well, there’s 8 bits in a byte and so our binary should have a multiple of 8 for the number of bits. However, we only have 268 digits which is not a multiple of 8. We’re off by 4 digits to get a whole number of bytes. This is because when we converted to binary, the leading zeros didn’t come with; you wouldn’t write 5 as 05 normally, perhaps only as formatting for a date. Similarly, converting to binary doesn’t provide these leading zeros, but converting to bytes does. However, we can just add the 4 zeros to the start by ourselves to pad it correctly for a whole number of bytes. Doing this, we end up with
}2517ca3a_do0g_0n_N_11ams{ymedaca
which when reversed gives
academy{sma11_N_n0_g0od_a3ac7152}
…And that’s our flag! This one was just a bunch of math and a test of whether one knows what RSA is. The point, however, was to show that we need an so large that it’s not feasibly factorable, which happens at around 2048 bits or so. NEXT!
Mini RSA
N: 1615765684321463054078226051959887884233678317734892901740763321135213636796075462401950274602405095138589898087428337758445013281488966866073355710771864671726991918706558071231266976427184673800225254531695928541272546385146495736420261815693810544589811104967829354461491178200126099661909654163542661541699404839644035177445092988952614918424317082380174383819025585076206641993479326576180793544321194357018916215113009742654408597083724508169216182008449693917227497813165444372201517541788989925461711067825681947947471001390843774746442699739386923285801022685451221261010798837646928092277556198145662924691803032880040492762442561497760689933601781401617086600593482127465655390841361154025890679757514060456103104199255917164678161972735858939464790960448345988941481499050248673128656508055285037090026439683847266536283160142071643015434813473463469733112182328678706702116054036618277506997666534567846763938692335069955755244438415377933440029498378955355877502743215305768814857864433151287
e: 3
ciphertext (c): 5709720175026317841944505166332182779419760031115432215563425901875620052791608163398750297304277886495537367793006288112991932717211066611899319963701838062828209254458935983141207100322897221021063116398621664612418813778554568264340430225832578419510188716272485416847020031786240493474216219976022230869888952027678216917573096647103329007747318226924546155390806085120914969452892742334587155888067115809741247942520249413550823776542714197976954393983964547415885494497967317041948018762619837565110934913856322148114605081220228082382496419481186347789827332766319099768385339675485674098729928722491454024819209643705436617193628436345338680537047177323301194162660757103489761598885882853346119600569195748535459069143523542108427003139728980057083066664641150644821647353430627540846457261643465679095174704766947857556896761656169629741900371937539062751103533758611539748595561989792317060484717828643461989154370495487728398479845553917495920818130003766392290200672893336450122809476169268494312
If you were paying attention, the ciphertext when RSA done normally cannot be bigger than . However, here the ciphertext is. I don’t know what happened here but it doesn’t seem like the value for the ciphertext was done correctly. Let’s solve it anyways.
Recall the definition of ciphertext .
Notice how that if doesn’t exceed , then the public exponent is able to be used to get the original message since it doesn’t wrap around; just take the -th root of . Here, obviously it didn’t wrap around. Therefore we can just take the -th root here, which is also relatively quick since is so small. However, since we’re dealing with such a large number, it’d be easier to write our own function to cube root.
c = 5709720175026317841944505166332182779419760031115432215563425901875620052791608163398750297304277886495537367793006288112991932717211066611899319963701838062828209254458935983141207100322897221021063116398621664612418813778554568264340430225832578419510188716272485416847020031786240493474216219976022230869888952027678216917573096647103329007747318226924546155390806085120914969452892742334587155888067115809741247942520249413550823776542714197976954393983964547415885494497967317041948018762619837565110934913856322148114605081220228082382496419481186347789827332766319099768385339675485674098729928722491454024819209643705436617193628436345338680537047177323301194162660757103489761598885882853346119600569195748535459069143523542108427003139728980057083066664641150644821647353430627540846457261643465679095174704766947857556896761656169629741900371937539062751103533758611539748595561989792317060484717828643461989154370495487728398479845553917495920818130003766392290200672893336450122809476169268494312
def cube_root(x):
lower, upper = 1, x
mid = (lower+upper)//2
while (mid**3!=x):
if (mid**3 > x):
upper=mid-1
else:
lower=mid+1
mid = (lower+upper)//2
return mid
print(cube_root(c))
# prints 1787330808968142828287809319332701517353332911736848279839502759158602467824780424488141955644417387373185756944952906538004355347478978500948630620749868180414755933760446136287315896825929319145984883756667607031853695069891380871892213007874933643544014803829752116973952588637032204981055419406087847822034466877741943243746534587658
Now, let’s see if actually means anything. Converting to bits and then text, we get
µåí}Í ÁÔÅ}Í}±ÉÍÉ|ÌÕÈÙÅ
ô
which doesn’t look right. The question said it was padded, and in binary we’re missing 2 bits to form a whole byte, so if we add one more 0 and remove the space padding, the text becomes
academy{e_sh0u1d_b3_lArg3r_35f26e1a}
BANG!!! Well, this one was also straightforward. But let’s look at what it would have been like if the ciphertext was taken modulo like it was supposed to.
N: 1615765684321463054078226051959887884233678317734892901740763321135213636796075462401950274602405095138589898087428337758445013281488966866073355710771864671726991918706558071231266976427184673800225254531695928541272546385146495736420261815693810544589811104967829354461491178200126099661909654163542661541699404839644035177445092988952614918424317082380174383819025585076206641993479326576180793544321194357018916215113009742654408597083724508169216182008449693917227497813165444372201517541788989925461711067825681947947471001390843774746442699739386923285801022685451221261010798837646928092277556198145662924691803032880040492762442561497760689933601781401617086600593482127465655390841361154025890679757514060456103104199255917164678161972735858939464790960448345988941481499050248673128656508055285037090026439683847266536283160142071643015434813473463469733112182328678706702116054036618277506997666534567846763938692335069955755244438415377933440029498378955355877502743215305768814857864433151287
e: 3
ciphertext (c): 1220012318588871886132524757898884422174534558055593713309088304910273991073554732659977133980685370899257850121970812405700793710546674062154237544840177616746805668666317481140872605653768484867292138139949076102907399831998827567645230986345765474386082421144307534571699205194983368689411816226007643064954729215840635659583117133740500954205974875390057358188693046676903289930281540940412295980336146393416954525985992752798203045915510615113622948111778806320744724053802074960057287479336158454709711228187826016190033306377026203314361301932347821092319618619935053234187382079077148713322674442826911883069528540253556263918866573750163001632083631388027202763984747153601103043353896172647828985898572944046802007552386765619056890053190423953960201377144265891393217286098978682913818684143643056111293363915464884208356874230514968369175935792624184018193591389668970019543078419942628261961962015259372158970475685574715201244632387257077193912230917119974983481113661168899916641126944997341
It’s obvious to see that the ciphertext is not a cube root, so we can’t simply take the cube root and expect the flag. However, from the definition of modulus arithmetic, . Thus, we can simply bruteforce what could be by iterating k=1,k=2,k=… . This is a valid way for this one since the problem states that is just barely larger than N.
for k in range(max_k):
candidate = c + k * N
m = iroot(candidate, e)
if m**e == candidate:
break