CAF 2023 - Tower of Encryption
Table of Contents
In this article, we will explore the step-by-step walkthrough of the Cryptography challenge ‘Tower of Encryption’ presented at the CTF Cyber Africa Forum 2023.
Challenge File
We have a challenge_file.txt file that contains the following data:
JJFEMRKNKJFU4S2KIZKTIUZSJNEVUS2UJFKVUU2KJZCVMVKTGJKUURSLKZKVKMSLJJNEGVSNKZFVIR2KJRCVOVCTJRFVUS2WJNGVGSKKJJDEKR2WKNHEWWSGKZFVGS2PJFJEGVSLKZBVISSWIVLE2USDK5EVMSKUIVJTES2KJJCEKS2TJNLUUTSHIVLVOU2HJNNEKVSFJVJUYSSGJJCUOVSTJRFVESSVLFJVGU2JLJGEKS2VJJJUWNKGIZGVGS2VJFLEUVCFKMZEYSSKINCUWUZSKRFE4TCVKVKFGSCJKZGFMS2VGJEEUTSOIVEVMU2IJNGVURSBKNJUOSSKINLE6VSTKRFEERSWIVJVGVSMIZFFMR2WGJFEYSSHIVFU2U2WJJFEUVKXJZFUOSKVGJDEKTKTLBFEKMSVKVLEGRSLJZFFKUKTGIZESUSLKVLVKWSTJNHEIVKVKNJVMSZVJNDEOU2LJJFVUR2GJVJDEVSHJJCUKVKUKNHUSVSGKZGVKMSHJJFEYRKVKZFVUS2OJJKVOU2TJNEU4TCFGZLFGVCKJZDFIS2SKNLUWTSMKZDVEMSLJFNEMRSNKIZFKR2KIZCVSU2TJBEVMRSVGJKTEV2KJJDEKVKWJNMEUWSGKVHVGSZSJE2UYRKPKZFFGS22IZCU2VCDIZFVMTCFI5JFGTCKKZHEKS2WGJKUSTSGKVJVMU2EJNLEYVSLK5JVISSOJRCU2USKKVFVEQSWJVJVUVSKJZCEKT2VKJJUUSSGKZKVEMSHJJLEURCFKZBU2SSKIZDEWVSLKNFU4SCVKNLFGR2LLJDVMS2NKNFEUSSIIVHVMU2WJNFEMRSPKMZE6SK2IRCTEVJSKREU4RKVGRKEGRSHLJGEMR2WINGEWSSEIZFVES2TJNHEYVKTKZJUWS22IRLE2VJSJZFEMTSFJVJFGTSLJFNEMR2SGJFUSWSDKZJVKMSUJJBEKVSNKJJVOSJVI5LEOVRSJNFFURKFJNJDEU2KJZGUKVKXKNDUWVSIKZDU2U2YJJCTEVKZKJFUYS2SIZLESVCDINEVUQ2VK5KEWVCLLJDUKTKUINDEWNKMKVDVIQ2LJRFEIRKNKMZFMSSOJNCU6U2LGJEVMSSWI5KTERKKJZGEKWKWKNGEWNKKKZEVGU2OI5FEWVKTKZJUYS22IZKTIU2TK5FFMSSWKVLFGS2KLJBUKS2UGJLEOSSHIVKVGU2HJNNEWVSFJVJUQSSKJRCVKVSLLBFVUSSVKFJVGS2JJJGEKV2WKNGEWTSGKU2FEU2WJNHEYRSHKNBUYTCKIRLE2UJSKZDUUS2VKVJVGSKJKZBVMRKTGJHUUTSGIVCVMS22JNHEEVSNKIZDESSOINCUWVKDJNDVMRKGJNIEUNKIKU3FIMSQJE6Q: ===
Initial Analysis
At first glance, we can notice that the character string has only uppercase letters and ==== at the end, which already gives me the hypothesis that this string is encoded in base32.
By going to CyberChef , I decide to decode base32 8 times, since it’s a chain of encodings.
I get this: JAH_{I0Y_F4U_H4EU3H_WO3_W0TVH}
This strangely resembles the flag format. And it looks like a substitution is applied to the string.
Solution 1 (Not intended for this challenge):
Python Script for Vigenère Cipher
I decide to create a Python script that will propose potential solutions from the prefix CAF_{:
import sys
import string
given_string: "JAH_{I0Y_F4U_H4EU3H_WO3_W0TVH}"
pattern: "CAF_{"
def decrypt_vigenere_cipher_partial(text, key):
decrypted_text: []
key_length: len(key)
for i, char in enumerate(text):
if i < key_length:
if char.isalpha():
shift: ord(key[i]) - ord('A')
if char.islower():
decrypted_text.append(chr((ord(char) - ord('a') - shift) % 26 + ord('a')))
else:
decrypted_text.append(chr((ord(char) - ord('A') - shift) % 26 + ord('A')))
else:
decrypted_text.append(char)
else:
decrypted_text.append(char)
return "".join(decrypted_text)
def find_key_recursive(partial_key, max_key_length, found_decryptions):
partial_decryption: decrypt_vigenere_cipher_partial(given_string, partial_key)
if partial_decryption.startswith(pattern) and partial_decryption not in found_decryptions:
print(f"Found partial key: {partial_key}, decryption: {partial_decryption}")
found_decryptions.append(partial_decryption)
sys.exit(0)
if len(partial_key) < max_key_length:
for next_char in string.ascii_uppercase:
find_key_recursive(partial_key + next_char, max_key_length, found_decryptions)
def main():
max_key_length: 6
found_decryptions: []
for key_length in range(1, max_key_length + 1):
find_key_recursive("", key_length, found_decryptions)
if __name__ == "__main__":
main()
Code Explanation
-
decrypt_vigenere_cipher_partial(text, key): This function has been rewritten to partially decrypt a text string encrypted with the Vigenère cipher using the given key. Decryption is performed only on characters whose position corresponds to the length of the provided partial key. -
find_key_recursive(partial_key, max_key_length, found_decryptions): This function performs a recursive traversal to generate all possible key combinations based on the given partial key. This partial key is initially empty and comes from themain()function. -
The
find_key_recursive()function works as follows:- It partially decrypts the
given_stringusing the provided partial key and stores the result inpartial_decryption. - If
partial_decryptionstarts with the searched pattern and is not already present in thefound_decryptionslist, the function displays the partial key and partial decryption, adds the partial decryption to thefound_decryptionslist, and terminates the program. - If the length of the partial key is less than
max_key_length, the function recursively traverses all possible key combinations by adding an uppercase ASCII character to the end of the current partial key.
- It partially decrypts the
-
main(): This function serves as the entry point for the program. It initializes the variablesmax_key_length(maximum key length) andfound_decryptions(list of already found decryptions), then calls thefind_key_recursive()function with an empty partial key for each possible key length, from1tomax_key_length.
Execution Result
Here is the result we get when executing it:
$ python flag.py
Found partial key: HAC, decryption: CAF_{I0Y_F4U_H4EU3H_WO3_W0TVH}
Generally, flags have meanings, and here it’s absolutely unreadable even though the prefix is there.
I go back to CyberChef and start trying words starting with HAC like HACK, which gives me better results, then I try HACKED. Using this key, we finally get a conclusive result:
The flag is: CAF_{Y0U_C4N_H4CK3D_TH3_W0RLD}
Solution 2:
RSA Key File
After obtaining the flag, I notice that I had forgotten to exploit another challenge file key_file.txt which contains the following data:
n: 1000000016000000063
e: 23
c: 471055156725181012
This strongly resembles values used for RSA encryption.
RSA Decryption Process
The vulnerability exploited here is the factorization of a large number using a classical factorization method. In this case, the number is n = p*q, where p and q are very large and unknown prime numbers. If we manage to factorize n into its prime factors p and q, we can easily calculate the private key d and decrypt the message c.
In this example, we factorized n using the factorint() function from the sympy library. Then, we calculated the value of phi_n, which is Euler’s totient function of n = (p-1)*(q-1). We then calculated the private key d using the mod_inverse() function from the sympy library (which by the way uses the C library msieve, widely used in CTF).
Finally, we used Python’s pow() function to decrypt the message c using the private key d and the modulo n.
Python Implementation
Here is the complete Python code to decrypt the message:
from sympy import factorint, mod_inverse
n: 1000000016000000063
e: 23
c: 471055156725181012
m: ''
factors: factorint(n)
p: list(factors.keys())[0]
q: int(n / p)
phi_n: (p - 1) * (q - 1)
d: mod_inverse(e, phi_n)
m_int: pow(c, d, n)
while m_int > 0:
m += chr(m_int % 1000)
m_int //= 1000
m: m[::-1]
print("p =", p)
print("q =", q)
print("d =", d)
print("Decrypted message:", m)
Execution Result
By executing this code:
$ python flag.py
p: 1000000007
q: 1000000009
d: 130434784434782615
Message déchiffré : hacked
We get the key hacked as in the first solution.
As in the first solution, we decrypt the partial flag with CyberChef using the key hacked:
The flag is: CAF_{Y0U_C4N_H4CK3D_TH3_W0RLD}