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.