Challenge Synopsis#
Hidden deep in the forest was an ancient scroll, rumored to grant immense power to anyone who could read its shifting symbols. On Halloween, a curious traveler found the scroll, its letters strangely out of order. As they deciphered the message, the words slowly rearranged themselves, revealing a dark spell. But with the final shift, the traveler felt a cold presence behind them, whispering, “You were never meant to understand.” The forest grew silent, but the spell was already cast. (Source)
Solution#
❯ cat source.py
from random import choices
import os
def julius_encrypt(msg, shift):
ct = ''
for p in msg:
if p == ' ':
ct += '0'
elif not ord('A') <= ord(p) <= ord('Z'):
ct += p
else:
o = ord(p) - 65
ct += chr(65 + (o + shift) % 26)
return ct
def encrypt(msg, key):
for shift in key:
msg = julius_encrypt(msg, shift)
return msg
msg = open('secret.txt').read().upper()
secure_key = os.urandom(1337)
with open('output.txt', 'w') as f:
f.write(encrypt(msg, secure_key))
❯ cat output.txt
JRYPBZR0GB0UNPXGUROBB0GJBGUBHFNAQGJRAGLSBHE!0GUVF0VF0N0CEBBS0BS0PBAPRCG0GB0CEBIR0LBH0GUNG0GUR0PNRFNE0PVCURE0VF0VAFRPHER0AB0ZNGGRE0UBJ0ZNAL0GVZRF0LBH0NCCYL0VG.0GUR0FRPHEVGL0BS0N0GUBHFNAQ0QVFGVAPG0FUVSGF0VF0RIRAGHNYYL0GUR0FNZR0NF0GUNG0BS0N0FVATYR0FUVSG.0RABHTU0ZHZOYVAT,0GNXR0LBHE0SYNT0NAQ0RAWBL0GUR0ERFG0BS0GUR0PBAGRFG.0ZNXR0FHER0LBH0JENC0GUR0SBYYBJVAT0GRKG0JVGU0GUR0UGO0SYNT0SBEZNG0GURRSSRPGVIRXRLFCNPRBSPNRFNEQRCRAQFBAGURFVMRBSGURNYCUNORG.
Even though the secure_key
is long and randomly generated (which should make decryption hard), repeated Caesar shifts eventually reduce to a single effective Caesar shift. This is because the Caesar cipher is modular and commutative:
So ultimately, the whole encryption just becomes one Caesar shift by the sum of all the shifts modulo 26.
❯ cat solve.py
#!/usr/bin/python3
def julius_decrypt(ct, shift):
pt = ''
for c in ct:
if c == '0':
pt += ' ' # Replace '0' back to space
elif not ord('A') <= ord(c) <= ord('Z'):
pt += c # Non-alphabetic characters remain unchanged
else:
o = ord(c) - 65
pt += chr(65 + (o - shift) % 26)
return pt
def decrypt():
enc = open('output.txt').read()
for i in range(1, 26): # Iterate over all possible shift values
print(f'{i = } | {julius_decrypt(enc, i)}')
if __name__ == "__main__":
decrypt()
❯ ./solve.py
...
i = 13 | WELCOME TO HACKTHEBOO TWOTHOUSANDTWENTYFOUR! THIS IS A PROOF OF CONCEPT TO PROVE YOU THAT THE CAESAR CIPHER IS INSECURE NO MATTER HOW MANY TIMES YOU APPLY IT. THE SECURITY OF A THOUSAND DISTINCT SHIFTS IS EVENTUALLY THE SAME AS THAT OF A SINGLE SHIFT. ENOUGH MUMBLING, TAKE YOUR FLAG AND ENJOY THE REST OF THE CONTEST. MAKE SURE YOU WRAP THE FOLLOWING TEXT WITH THE HTB FLAG FORMAT THEEFFECTIVEKEYSPACEOFCAESARDEPENDSONTHESIZEOFTHEALPHABET.
...
Flag: HTB{THEEFFECTIVEKEYSPACEOFCAESARDEPENDSONTHESIZEOFTHEALPHABET}