1533 words
8 minutes
TexSAW 2026 - The Imitation Game - Cryptography Writeup

Category: Cryptography Flag: texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}

Challenge Description#

There’s a spy amongst us! We found one of their messages, but can’t seem to crack it. For some reason, they wrote the message down twice.

Analysis#

This challenge came as a single ASCII text file, and the important clue was structural rather than binary. The solve log showed two ciphertext sections with matching punctuation, spacing, and line lengths, plus two different flag-like strings at the top. That strongly suggested the same plaintext had been encrypted twice instead of two unrelated messages being present in the file.

To verify that, I compared the two prose blocks line by line and checked whether their word-length patterns matched exactly. The short Python snippet below reads the ciphertext file, splits it into the two blocks, and prints the word lengths for each aligned pair of lines.

# compare_structure.py
from pathlib import Path

text = Path('/home/rei/Downloads/TheImitationGame/ciphertext.txt').read_text().splitlines()
block1 = [l for l in text[2:10] if l]
block2 = [l for l in text[13:21] if l]

print('block1 lines', len(block1), 'block2', len(block2))
for a, b in zip(block1, block2):
    wa = a.split()
    wb = b.split()
    print('line lengths', len(a), len(b), 'words', [len(x) for x in wa], [len(x) for x in wb])
    print('same pattern?', [len(x) for x in wa] == [len(x) for x in wb])
python compare_structure.py
block1 lines 7 block2 7
line lengths 79 79 words [4, 4, 2, 5, 3, 3, 4, 1, 6, 2, 4, 6, 4, 4, 3, 3, 1, 3] [4, 4, 2, 5, 3, 3, 4, 1, 6, 2, 4, 6, 4, 4, 3, 3, 1, 3]
same pattern? True
line lengths 54 54 words [4, 3, 7, 4, 3, 4, 2, 2, 4, 2, 9] [4, 3, 7, 4, 3, 4, 2, 2, 4, 2, 9]
same pattern? True
line lengths 54 54 words [4, 3, 7, 6, 4, 4, 3, 5, 2, 7] [4, 3, 7, 6, 4, 4, 3, 5, 2, 7]
same pattern? True
line lengths 83 83 words [2, 3, 4, 5, 4, 4, 3, 2, 3, 5, 7, 8, 8, 2, 9] [2, 3, 4, 5, 4, 4, 3, 2, 3, 5, 7, 8, 8, 2, 9]
same pattern? True
line lengths 29 29 words [4, 4, 6, 3, 8] [4, 4, 6, 3, 8]
same pattern? True
line lengths 79 79 words [4, 3, 5, 10, 5, 2, 4, 5, 3, 6, 5, 2, 4, 3, 4] [4, 3, 5, 10, 5, 2, 4, 5, 3, 6, 5, 2, 4, 3, 4]
same pattern? True
line lengths 17 17 words [1, 4, 10] [1, 4, 10]
same pattern? True

That was enough to rule in “same plaintext twice.” A simple monoalphabetic relation between the two ciphertexts did not hold, so the next useful anchor was the known flag prefix texsaw{}. Using the first six letters of the two brace-wrapped ciphertext strings against the known plaintext texsaw, I recovered two short key fragments.

# derive_prefix_key.py
import string

A = {c: i for i, c in enumerate(string.ascii_lowercase)}
flag1 = 'twhsnz'
flag2 = 'brassg'
pt = 'texsaw'

for name, ct in [('k1', flag1), ('k2', flag2)]:
    key = ''.join(string.ascii_lowercase[(A[c] - A[p]) % 26] for c, p in zip(ct, pt))
    print(name, key)
python derive_prefix_key.py
k1 askand
k2 indask

Those fragments were the real giveaway. They look like cyclic slices of the same Vigenere key rather than two unrelated keys, which fits the prompt hint that the message was written down twice. From there, the solve modeled the two ciphertext streams as the same plaintext encrypted with the same repeating key but with different starting offsets into that key. By stripping both blocks down to letters only and comparing the per-position differences modulo a candidate key length of 41, it was possible to recover a recurrence for the full key and test each possible shift until both prefix fragments lined up.

# recover_key.py
from pathlib import Path
import string

A = {c: i for i, c in enumerate(string.ascii_lowercase)}
alpha = string.ascii_lowercase
text = Path('/home/rei/Downloads/TheImitationGame/ciphertext.txt').read_text().splitlines()
s1 = ''.join(ch for l in text[:10] for ch in l.lower() if ch.isalpha())
s2 = ''.join(ch for l in text[11:] for ch in l.lower() if ch.isalpha())
L = 41
D = []

for j in range(L):
    vals = {(A[b] - A[a]) % 26 for i, (a, b) in enumerate(zip(s1, s2)) if i % L == j}
    print(j, vals)
    if len(vals) != 1:
        raise SystemExit('not single-valued')
    D.append(vals.pop())

print('D letters', ''.join(alpha[x] for x in D))
known = {0: A['a'], 1: A['s'], 2: A['k'], 3: A['a'], 4: A['n'], 5: A['d']}
known2 = 'indask'

for s in range(1, L):
    K = [None] * L
    K[0] = 0
    stack = [0]
    ok = True
    while stack and ok:
        j = stack.pop()
        nj = (j + s) % L
        val = (K[j] + D[j]) % 26
        if K[nj] is None:
            K[nj] = val
            stack.append(nj)
        elif K[nj] != val:
            ok = False
    if not ok or any(v is None for v in K):
        continue
    poss = None
    for idx, v in known.items():
        t = (v - K[idx]) % 26
        if poss is None:
            poss = t
        elif poss != t:
            ok = False
            break
    if not ok:
        continue
    KK = [(x + poss) % 26 for x in K]
    kstr = ''.join(alpha[x] for x in KK)
    second = ''.join(alpha[KK[(s + i) % L]] for i in range(6))
    print('shift', s, 'key', kstr, 'secondseg', second, 'ok2', second == known2)
python recover_key.py
D letters ivtafhsulbthwzhftjcvxqtgkqierhcjlrehwvdyc
shift 38 key askanditshallbegivenyouseekandyeshallfind secondseg indask ok2 True

With the key askanditshallbegivenyouseekandyeshallfind and second-block offset 38 recovered, the rest was just a straightforward Vigenere decryption. Decrypting both blocks produced the same plaintext, which confirmed the model completely and exposed the real flag on the first line of each decrypted copy.

# decrypt_blocks.py
from pathlib import Path
import string

alpha = string.ascii_lowercase
A = {c: i for i, c in enumerate(alpha)}
key = 'askanditshallbegivenyouseekandyeshallfind'
shift = 38

def dec_lines(lines, start=0):
    out = []
    j = 0
    L = len(key)
    for line in lines:
        row = []
        for ch in line:
            if ch.isalpha():
                k = A[key[(start + j) % L]]
                row.append(alpha[(A[ch] - k) % 26])
                j += 1
            else:
                row.append(ch)
        out.append(''.join(row))
    return out

lines = Path('/home/rei/Downloads/TheImitationGame/ciphertext.txt').read_text().splitlines()
p1 = dec_lines(lines[:10], 0)
p2 = dec_lines(lines[11:], shift)
print('---BLOCK1---')
print('\n'.join(p1))
print('---BLOCK2---')
print('\n'.join(p2))
python decrypt_blocks.py
---BLOCK1---
texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}

they know im here, and its only a matter of time before they find out who i am.
tell the general what the flag is as soon as possible.
tell the general before they find out where im hiding!
if all goes well, i'll meet you at our first meeting location tomorrow at midnight.
make sure you're not followed

p.s. the movie "imitation game" is very good. you should watch it when you can.
- john cairncross
---BLOCK2---
texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}

Solution#

The solve used two short Python scripts in sequence: one to recover the repeated Vigenere key and the offset between the two ciphertext copies, and one to decrypt both blocks with those recovered values.

# recover_key.py
from pathlib import Path
import string

A = {c: i for i, c in enumerate(string.ascii_lowercase)}
alpha = string.ascii_lowercase
text = Path('/home/rei/Downloads/TheImitationGame/ciphertext.txt').read_text().splitlines()
s1 = ''.join(ch for l in text[:10] for ch in l.lower() if ch.isalpha())
s2 = ''.join(ch for l in text[11:] for ch in l.lower() if ch.isalpha())
L = 41
D = []

for j in range(L):
    vals = {(A[b] - A[a]) % 26 for i, (a, b) in enumerate(zip(s1, s2)) if i % L == j}
    if len(vals) != 1:
        raise SystemExit('not single-valued')
    D.append(vals.pop())

known = {0: A['a'], 1: A['s'], 2: A['k'], 3: A['a'], 4: A['n'], 5: A['d']}
known2 = 'indask'

for s in range(1, L):
    K = [None] * L
    K[0] = 0
    stack = [0]
    ok = True
    while stack and ok:
        j = stack.pop()
        nj = (j + s) % L
        val = (K[j] + D[j]) % 26
        if K[nj] is None:
            K[nj] = val
            stack.append(nj)
        elif K[nj] != val:
            ok = False
    if not ok or any(v is None for v in K):
        continue
    poss = None
    for idx, v in known.items():
        t = (v - K[idx]) % 26
        if poss is None:
            poss = t
        elif poss != t:
            ok = False
            break
    if not ok:
        continue
    KK = [(x + poss) % 26 for x in K]
    kstr = ''.join(alpha[x] for x in KK)
    second = ''.join(alpha[KK[(s + i) % L]] for i in range(6))
    if second == known2:
        print('shift', s, 'key', kstr)
        break
python recover_key.py
shift 38 key askanditshallbegivenyouseekandyeshallfind
# decrypt_blocks.py
from pathlib import Path
import string

alpha = string.ascii_lowercase
A = {c: i for i, c in enumerate(alpha)}
key = 'askanditshallbegivenyouseekandyeshallfind'
shift = 38

def dec_lines(lines, start=0):
    out = []
    j = 0
    L = len(key)
    for line in lines:
        row = []
        for ch in line:
            if ch.isalpha():
                k = A[key[(start + j) % L]]
                row.append(alpha[(A[ch] - k) % 26])
                j += 1
            else:
                row.append(ch)
        out.append(''.join(row))
    return out

lines = Path('/home/rei/Downloads/TheImitationGame/ciphertext.txt').read_text().splitlines()
p1 = dec_lines(lines[:10], 0)
p2 = dec_lines(lines[11:], shift)
print('\n'.join(p1))
print()
print('\n'.join(p2))
python decrypt_blocks.py
texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}

they know im here, and its only a matter of time before they find out who i am.
tell the general what the flag is as soon as possible.
tell the general before they find out where im hiding!
if all goes well, i'll meet you at our first meeting location tomorrow at midnight.
make sure you're not followed

p.s. the movie "imitation game" is very good. you should watch it when you can.
- john cairncross

texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}
TexSAW 2026 - The Imitation Game - Cryptography Writeup
https://blog.rei.my.id/posts/125/texsaw-2026-the-imitation-game-cryptography-writeup/
Author
Reidho Satria
Published at
2026-03-30
License
CC BY-NC-SA 4.0