I took an old cipher and modified it to make it secure again. By breaking the regular patterns I made sure the known attacks don’t work. Now I can use it to encrypt texts with my favourite Chat partner.

Inspecting chall.py and running it with a custom text.txt and flag.txt, we see that every token (substring of lowercase characters separated by spaces) is Caesar shifted by an amount to produce output.txt. Specifically, the countth token is shifted by chars.index(flag[count % len(flag)]) for all tokens. We annotate the code below.

def caesar(msg, shift):
	return ''.join(chars[(chars.index(c) + shift) % len(chars)] for c in msg)

i = 0
count = 0
while i < len(text):
	if text[i] not in string.ascii_lowercase:
		print(text[i], end = '')
		i += 1
	else:
		# Get the next token
		j = i
		while text[j] in string.ascii_lowercase: j += 1

		# Shift the token text[i:j]
		print(caesar(text[i:j], chars.index(flag[count % len(flag)])), end = '')
		count += 1
		i = j

Since each token is Caesar ciphered, we can recover each token by checking all possible len(chars) = 65 possible keys for each individual token and seeing which keys yields alphabetic English words.

For example, trying all shifts for the first token AtvDxK in output.txt yields the alphabetic plaintext : key pairs

piksmz : l
ohjrly : m
ngiqkx : n
mfhpjw : o
legoiv : p
kdfnhu : q
jcemgt : r
ibdlfs : s
hacker : t

Clearly, the first word of the decrypted output.txt is hacker, meaning the first character of the flag is t.

Running the following script to list the possible plaintext : key pairs and manually choosing the English words as well as corresponding keys yields the flag when concatenating all the keys together.

import string

text = "AtvDxK lAopjz /i + vhw c6 uwnshnuqjx ymfy kymhi Kyv 47+3l/eh Bs kpfkxkfwcnu Als 9phdgj9 +ka ymzuBGxmFq 6fdglk8i CICDowC, sjxir bjme+pfwfkd 6li=fj=kp, nCplEtGtEJ, lyo qeb INKLNBM vm ademb7697. ollqba lq DitCmA xzhm fx ef7dd7ii, wIvv eggiww GB kphqtocvkqp, 3d6 MAx ilsplm /d rpfkd vnloov hc nruwtAj xDxyjrx vexliv KyrE +3hc Gurz, jcemgt ixlmgw 9f7gmj5/9k obpmlkpf/ib mzp 8k/=64c ECo sj qb=eklildv. =k loGznlEpD qzC qo+kpm+obk=v, vHEEtuHKtMBHG, huk h7if75j/d9 mofs+=v, zkloh lqAkwCzioqvo rfqnhntzx fhynAnynjx b/a7 JKvrCzEx hexe BE ecwukpi 63c397. MAxLx wypujpwslz 3/c ql irvwhu 9bbcj1h9cb fsi f tswmxmzi zDGrtK ed FBpvrGL vjtqwij ixlmgep 5f8 =lkpqor=qfsb tmowuzs."

chars = string.ascii_letters + string.digits + '+/='


def caesar(msg, shift):
    return ''.join(chars[(chars.index(c) - shift) % len(chars)] for c in msg)


i = 0
count = 0
while i < len(text):
    if text[i] not in chars:
        i += 1
    else:
        j = i
        while j < len(text) and text[j] in chars: j += 1
        for k in range(len(chars)):
            word = caesar(text[i:j], k)
            if word.isalpha() and word.islower():
                print(word, ":", chars[k])
        print()
        count += 1
        i = j

Flag: ENO{th3_d1ffer3nce5_m4ke_4ll_th3_diff3renc3}