← Back to Blog

ADFGVX Project — Round 3/5

We have the substitution. Let's recap where we are before adding the second layer.

import string

all_chars = string.ascii_uppercase + string.digits

# Key 1 — substitution key
final_subst_key = ""
while len(final_subst_key) < 6:
    subst_key = input("Please enter substitution key: ")
    clean_subst_key = ""
    for letter in subst_key.upper():
        if letter in all_chars:
            clean_subst_key += letter
    final_subst_key = ""
    for letter in clean_subst_key:
        if letter not in final_subst_key:
            final_subst_key += letter
    if len(final_subst_key) < 6:
        print("Invalid key. Please try again.")

sorted_chars = ""
reference = final_subst_key + all_chars
for letter in reference:
    if letter not in sorted_chars:
        sorted_chars += letter

matrix_letters = [
    "AA", "AD", "AF", "AG", "AV", "AX",
    "DA", "DD", "DF", "DG", "DV", "DX",
    "FA", "FD", "FF", "FG", "FV", "FX",
    "GA", "GD", "GF", "GG", "GV", "GX",
    "VA", "VD", "VF", "VG", "VV", "VX",
    "XA", "XD", "XF", "XG", "XV", "XX"
]

matrix_dict = {}
for i in range(0, len(sorted_chars)):
    matrix_dict[sorted_chars[i]] = matrix_letters[i]

message = ""
while not message:
    message = input("Enter your message: ")

clean_message = ""
for letter in message.strip().upper():
    if letter in all_chars:
        clean_message += letter

substituted_message = ""
for letter in clean_message:
    substituted_message += matrix_dict[letter]

With substitution key WASHINGTON and message ATTACK AT DAWN, substituted_message is ADDADADAGDADADADVFADADAAVD — wait.

An important detail: every character in the original message becomes exactly two ADFGVX letters. So substituted_message is always even in length. ATTACKATDAWN has 12 characters — substituted_message has 24. Always double. Always even.

Let's verify:

A → AD
T → DA
T → DA
A → AD
C → DV
K → FG
A → AD
T → DA
D → GD
A → AD
W → AA
N → AX

substituted_message: ADDADADADVFGADDAGDADAAAX

24 characters. Even. Ready for transposition.

Transposition — the second layer

Substitution replaced each character with a pair. Transposition scrambles the order of those pairs using a second key.

The process: distribute the substituted message character by character across columns headed by the transposition key letters. Then read the columns in alphabetical order of the key — that's the final ciphertext.

To reverse it at decryption, the receiver needs both keys. Without them, the columns can't be reassembled. That's the second lock.

We need a second key. Same validation logic as the first — reused, renamed.

final_transp_key = ""
while len(final_transp_key) < 6:
    transp_key = input("Please enter transposition key: ")
    clean_transp_key = ""
    for letter in transp_key.upper():
        if letter in all_chars:
            clean_transp_key += letter
    final_transp_key = ""
    for letter in clean_transp_key:
        if letter not in final_transp_key:
            final_transp_key += letter
    if len(final_transp_key) < 6:
        print("Invalid key. Please try again.")

No duplicate characters — at decryption, if the key had duplicates, we wouldn't know which column came first. No spaces, no special characters — they'd interfere with alphabetical sorting. Minimum 6 unique characters — enough columns to make the transposition meaningful.

We initialize a dictionary — each character of final_transp_key as a key, empty string as value:

transp_dict = {}
for letter in final_transp_key:
    transp_dict[letter] = ""

Then we fill it — one character at a time, cycling through the columns:

col_index = 0
for char in substituted_message:
    transp_dict[final_transp_key[col_index]] += char
    if col_index == len(final_transp_key) - 1:
        col_index = 0
    else:
        col_index += 1

col_index starts at 0. For each character in the substituted message, we find the column letter at that index and append the character to its string. When we reach the last column, we reset to 0. Otherwise we increment. Simple cycling.

An important note: we don't always know how evenly the substituted message divides across the columns. 24 characters across 6 columns divides evenly — 4 each. But if the message were longer or shorter, some columns would get one character more than others. That's intentional — it was one of the elements that made ADFGVX harder to crack. Incomplete columns leave the analyst uncertain about the key length.

Simulation. Substitution key: WASHINGTON. Transposition key: BERLIN. substituted_message: 24 characters.

Characters distributed one by one across columns B, E, R, L, I, N:

Position:  0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23
Char:      A  D  D  A  D  A  D  A  D  V  F  G  A  D  D  A  G  D  A  D  A  A  A  X
Column:    B  E  R  L  I  N  B  E  R  L  I  N  B  E  R  L  I  N  B  E  R  L  I  N
B (0,6,12,18) → A,D,A,A  → ADAA
E (1,7,13,19) → D,A,D,D  → DADD
R (2,8,14,20) → D,D,D,A  → DDDA
L (3,9,15,21) → A,V,A,A  → AVAA
I (4,10,16,22)→ D,F,G,A  → DFGA
N (5,11,17,23)→ A,G,D,X  → AGDX

Total: 4×6 = 24 ✓

Columns sorted alphabetically by key letter B → E → I → L → N → R:

final_message = ""
for key in sorted(transp_dict):
    final_message += transp_dict[key]
    final_message += " "

print("Message coding complete!")
print(final_message)

Output:

ADAA DADD DFGA AVAA AGDX DDDA

That's the ciphertext. Twelve characters of original message — substituted, scrambled, sorted. Without both keys, it's noise.

We export it to a file:

import datetime
today_string = datetime.date.today().strftime("%Y_%m_%d")
file_name = f"coded_{today_string}.txt"

with open(file_name, "w", encoding="utf-8") as f:
    f.write(final_message)
print(f"Coded message exported to {file_name} successfully!")

The encoder is done. Before we build the decoder, we stop for an interlude.

[ login to bookmark ] // copied! 30 views · 4 min
// resources
Exercise adfgvx_encoder.py
← prev ADFGVX Project — Round 2/5 next → ADFGVX Project — Interlude
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.