← Back to Blog

ADFGVX Project — Round 4/5

The encoder works. We use it. We need something real to verify that deconstruction works correctly.

We work with these variables:

subst_key = WASHINGTON
transp_key = BERLIN
message   = Attack at dawn

Results from the encoder:

substituted_message = ADDDDDADDVFGADDDDDXADAAAX
final_message       = AAAA DDDD DFDA DVDA DGXX DDDA

Two steps to reverse. Starting from final_message, using transp_key, we recover substituted_message. That's step one. Then from substituted_message, using subst_key, we recover ATTACKATDAWN. That's step two. Keep these values in sight — we'll verify against them throughout.

Loading the coded message

We load from a file. The date is entered by the user — we assume the receiver knows the format and the rules. No format validation. Less error handling means less information for someone who shouldn't be reading this.

message_date = input("Please provide the date: ")
file_name = f"coded_{message_date}.txt"

try:
    with open(file_name, "r", encoding="utf-8") as f:
        message = f.read()
except FileNotFoundError:
    exit()

If the file doesn't exist — silent exit. No error message, no hint. Someone without the correct date gets nothing.

Transposition key — relaxed validation

At encoding, we required minimum 6 unique characters and clear error messages. Here we strip that down. No length limit. No explicit error message. If the key is invalid, the decoder produces nonsense — which is fine. Better nonsense than a clear signal to an intruder that they got the key wrong.

We ask for a key without identifying what it is. The person who knows how to use this program will know what's being asked and when.

import string

all_chars = string.ascii_uppercase + string.digits

final_transp_key = ""
while not final_transp_key:
    transp_key = input("Please enter key: ")
    clean_transp_key = ""
    for letter in transp_key.upper():
        if letter in all_chars:
            clean_transp_key += letter
    final_transp_key = ""
    if clean_transp_key:
        for letter in clean_transp_key:
            if letter not in final_transp_key:
                final_transp_key += letter

Building the sorted key list

We create a list of the key's characters and sort it alphabetically — this mirrors what the encoder did when it read columns in alphabetical order.

transp_list = []
for letter in final_transp_key:
    transp_list.append(letter)
transp_list.sort()

Note: transp_list.sort() sorts in place and returns None. Don't assign it — transp_list = transp_list.sort() would overwrite your list with nothing.

Splitting the coded message

The coded message is a series of blocks separated by spaces. We split it into a list.

message_list = message.split()

We verify that the number of blocks matches the number of key characters. If it doesn't — something is wrong with the key or the message. Instead of raising an error, we set subst_message to "Q" — a value that can't come from a valid ADFGVX transposition, since the cipher only uses A, D, F, G, V, X. Later, this gives us a clean way to detect and handle a failed decryption without revealing why it failed.

if len(message_list) == len(transp_list):

    transp_dict = {}
    for i in range(len(transp_list)):
        transp_dict[transp_list[i]] = message_list[i]

    ordered_transp_list = []
    for letter in final_transp_key:
        ordered_transp_list.append(transp_dict[letter])

    seq_lengths = []
    for seq in ordered_transp_list:
        seq_lengths.append(len(seq))

    len_reference = max(seq_lengths)

    subst_message = ""
    for i in range(0, len_reference):
        for seq in ordered_transp_list:
            try:
                subst_message += seq[i]
            except:
                pass

else:
    subst_message = "Q"

Let's walk through the logic.

We pair each letter from the sorted key list with its corresponding block from the message — that's transp_dict. Then we rebuild ordered_transp_list by iterating final_transp_key in its original order — this restores the original column arrangement before alphabetical sorting.

Then we reconstruct the substituted message. We find the longest sequence — len_reference — and iterate index by index across all sequences. At each index, we take one character from each column. try/except handles incomplete columns silently — if a sequence is shorter and the index doesn't exist, we skip it and continue. The except here isn't a placeholder. It's intentional — we want the loop to keep going without producing an error.

With key BERLIN and final_message = AAAA DDDD DFDA DVDA DGXX DDDA:

transp_list (sorted):    ['B', 'E', 'I', 'L', 'N', 'R']
message_list:            ['AAAA', 'DDDD', 'DFDA', 'DVDA', 'DGXX', 'DDDA']

transp_dict:
    B → AAAA
    E → DDDD
    I → DFDA
    L → DVDA
    N → DGXX
    R → DDDA

ordered_transp_list (by BERLIN):
    B → AAAA
    E → DDDD
    R → DDDA
    L → DVDA
    I → DFDA
    N → DGXX

Reconstructing index by index:

i=0: A, D, D, D, D, D
i=1: A, D, D, V, F, G
i=2: A, D, A, D, D, X
i=3: A, D, A, A, A, X

subst_message = ADDDDDADDVFGADDDDDXADAAAX

That matches. Step one complete.

Next — we reverse the substitution. Round 5.

[ login to bookmark ] // copied! 38 views · 3 min
← prev ADFGVX Project — Interlude next → ADFGVX Project — Round 5/5
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.