transposition-trial

Published: July 20, 2023

Description

Every block of three characters in the message was permuted: positions (0,1,2) became (2,0,1). Undo the pattern to read the plaintext flag.

Download message.txt and note that “The flag” appears if you reorder each triple of characters.

Write a short script (Python shown below) to iterate through the text in steps of three and rearrange the characters back to their original order.

wget https://artifacts.picoctf.net/c/191/message.txt
python3 - <<'PY'
flag = []
with open('message.txt') as f:
    data = f.read()
for i in range(0, len(data), 3):
    block = data[i:i+3]
    if len(block) == 3:
        flag.append(block[2] + block[0] + block[1])
print(''.join(flag))
PY

Solution

  1. Step 1Deduce the permutation
    Because the first word should read "The", you can infer the mapping. Each chunk `[a b c]` was scrambled to `[b c a]` (or equivalently, to recover the original, take `[c a b]`).
    Learn more

    A transposition cipher rearranges characters according to a fixed rule without changing what the characters are. In this case, every 3-character block [a, b, c] was permuted to [b, c, a] (a cyclic left rotation). To recover the original, apply the inverse: take position 2, then 0, then 1 from each ciphertext block, producing [c, a, b] → original [a, b, c].

    Known-plaintext reasoningmakes the mapping easy to determine: since English text likely starts with "The", the ciphertext must start with those same letters in scrambled order. Trying different permutations of "T", "h", "e" until the result reads "The" immediately reveals the mapping. This is why knowing any plaintext makes breaking transposition ciphers trivial.

    Block transposition was used historically in columnar transposition ciphers, where text was written into a grid and columns were read out in a specified order. The ADFGVX cipher used by Germany in WWI combined a substitution step with columnar transposition - French cryptanalyst Georges Painvin broke it under significant time pressure during the war.

  2. Step 2Automate the swap
    Loop through the ciphertext in steps of 3, reassemble each block as `block[2] + block[0] + block[1]`, and concatenate the results. The final sentence ends with picoCTF{...}.
    Learn more

    Python's range(0, len(data), 3) generates indices 0, 3, 6, 9, ... - stepping through the string in chunks of 3. Slicing data[i:i+3] extracts each block. This is the standard Python idiom for processing fixed-width records in a string or byte array.

    The edge case to handle: if the message length isn't a multiple of 3, the final block may have only 1 or 2 characters. The script's if len(block) == 3 check skips incomplete blocks - depending on the cipher implementation, a different handling rule may apply (padding, partial permutation, etc.).

    In modern cryptography, block ciphers like AES also operate on fixed-size blocks (128 bits for AES), and the handling of messages that don't divide evenly into blocks requires padding schemes (PKCS#7 is most common). Improperly implemented padding led to real vulnerabilities like padding oracle attacks against CBC mode encryption.

Flag

picoCTF{7R4N5P051N6_15_3XP3N51V3_56E6...}

Any language works; just be careful with the final block if the length isn’t divisible by three.

Want more picoCTF 2022 writeups?

Useful tools for Cryptography

Related reading

What to try next