In the spring of 1918, the German army was trying to solve a very modern problem with very limited tools.
It had radio. It had telegraphy. It had fast-moving operations that depended on rapid communication. But it also had a harsh new weakness: once a wireless message went into the air, anyone with the right equipment could intercept it. A signal no longer travelled down a private wire to one trusted destination. It spread outward into space, where friendly receivers could hear it, and so could enemy ones.
That changed the nature of secrecy. The question was no longer, Can the enemy intercept this? Increasingly, the answer was yes. The real question became: Can the enemy understand it quickly enough to matter?
Out of that pressure came two closely related German field ciphers: ADFGX and, soon afterward, ADFGVX.
These were not machine ciphers. They were not modern in the computational sense. They were hand systems: built for speed, trainability, radio conditions, and the exhausting reality of operators working under wartime pressure. But they contained a genuinely clever idea. Instead of simply replacing one plaintext letter with one ciphertext letter, they split each character into a pair of coordinate symbols and then scrambled those symbols by transposition.
That is what gives them their enduring fascination.
They still belong to the classical world of grids, keywords, and pencil-and-paper procedures. Yet they already point toward a more ambitious instinct, one later cryptography would value enormously: do not preserve visible structure if you can avoid it.
ADFGX and ADFGVX sit at that turning point. They are still recognisably classical, but they are trying to do something more than disguise. They are trying to damage the message's surface structure before the enemy can study it.
ADFGX and ADFGVX at a Glance
| Feature | ADFGX | ADFGVX |
|---|---|---|
| Era | World War I, 1918 | World War I, 1918 |
| Type | Fractionating field cipher | Fractionating field cipher |
| Core mechanism | 5x5 keyed square + columnar transposition | 6x6 keyed square + columnar transposition |
| Cipher alphabet | A, D, F, G, X | A, D, F, G, V, X |
| Direct character capacity | 25 symbols | 36 symbols |
| Usual plaintext set | Letters only, with I/J merged | Letters and digits |
| Main practical use | Alphabetic field traffic | More realistic traffic including numbers |
| Main weakness | Traffic volume, repetition, key reuse, analysis of structure | The same weaknesses, despite broader capacity |
Not Just Another Substitution Cipher
At first glance, both ciphers can look almost too simple.
ADFGX uses only five ciphertext letters:
A D F G X
ADFGVX uses six:
A D F G V X
That is it.
No punctuation. No rich alphabet soup. No machine-like chaos. Just a very small symbol set, repeated over and over in tight blocks.
And yet both ciphers are more interesting than they first appear because they are two operations fused together:
- substitution by coordinates using a keyed square
- columnar transposition using a second key
Either stage on its own would be vulnerable. Together, they become much more troublesome, especially for a hand system used in live wartime traffic.
That combination matters because ordinary substitution ciphers preserve too much of the original language. Common plaintext letters remain common in disguise. Frequent words still leave statistical fingerprints. Familiar structure leaks through.
ADFGX and ADFGVX attack that problem by changing the unit of concealment.
They do not merely hide letters. They fracture them.
A plaintext character becomes a pair of coordinate symbols, and those symbols are then passed through a transposition stage that separates them from one another. The original visible units no longer survive in one clean layer.
That is the move that gives these ciphers their character.
Why Those Letters?
The names come directly from the letters used in the ciphertext alphabets.
ADFGX uses: A, D, F, G, X
ADFGVX uses: A, D, F, G, V, X
These symbols were commonly chosen because their Morse code patterns were relatively distinct, helping reduce transmission errors in radio conditions. Whether one leans heavily or cautiously on that explanation, the larger point is clear: these ciphers were not abstract classroom designs. They were shaped by the practical demands of wartime communication.
That is part of why they are so appealing to study.
They are not just cryptographic systems in the abstract. They are systems designed under pressure, balancing:
- secrecy
- speed
- trainability
- field usability
- reliable wireless transmission
ADFGVX simply extends the same logic by adding V, because the later 6x6 version needed a sixth row and column label.
One Idea, Two Variants
The cleanest way to understand these ciphers is this:
ADFGX and ADFGVX are the same core design in two different sizes.
ADFGX
- uses a 5x5 square
- therefore fits 25 symbols
- usually merges I and J
- handles letters only in its standard form
ADFGVX
- uses a 6x6 square
- therefore fits 36 symbols
- carries
A-Zand0-9 - is much better suited to real military traffic, where numbers matter constantly
So ADFGX is the earlier, tighter, more limited version.
ADFGVX is the expanded version that admits an obvious wartime reality: armies do not communicate only in words. They also communicate in dates, quantities, map references, train numbers, unit numbers, timings, and coordinates.
The Shared Mechanism
Before splitting the discussion in two, it helps to see the common structure.
Step 1: Build a keyed square
Each plaintext character is placed in a square.
- In ADFGX, the square is 5x5
- In ADFGVX, the square is 6x6
The rows and columns are labelled with the letters of the cipher alphabet:
ADFGX-> rows and columns labelledA D F G XADFGVX-> rows and columns labelledA D F G V X
A plaintext character is encrypted by writing down:
- its row label first
- its column label second
So a character might become:
AF
GX
DV
XA
depending on its location.
Step 2: Convert the message into coordinate pairs
This is the fractionating stage.
A plaintext message is no longer treated as a stream of letters or digits. It becomes a stream of coordinate pairs drawn from a very small symbol set.
That alone already changes the appearance of the message dramatically.
Step 3: Apply columnar transposition
The coordinate stream is written row by row under a keyword.
The keyword's columns are then alphabetised, the columns are reordered accordingly, and the ciphertext is read out column by column.
That is the stage that tears apart the original pairs.
A pair like AF may not survive as AF in the final ciphertext. Its two symbols may end up far apart. That is the real sting in the design.
ADFGX: The 5x5 Version
ADFGX was introduced by the German army in early 1918 and is usually associated with the signal officer Fritz Nebel.
It uses a 5x5 Polybius-style square, which means one letter has to give way. In practice, I and J are usually merged into a single cell.
So the alphabet becomes a 25-character system such as:
A B C D E F G H I/J K L M N O P Q R S T U V W X Y Z
But the square is not normally written in plain alphabetical order. It is mixed using a keyword or full mixed alphabet.
A sample square might look like this:
A D F G X
A | B T A L P
D | D H O Z K
F | Q F V S N
G | G I C U X
X | M R E W Y
This is only an example, but it shows the mechanism clearly.
If C sits at row G, column F, then:
C -> GF
If T sits at row A, column D, then:
T -> AD
That gives the first transformation: letters become coordinate pairs.
Worked Example: ADFGX Step by Step
Let us run through the full 5x5 version.
We will use the square:
A D F G X
A | B T A L P
D | D H O Z K
F | Q F V S N
G | G I C U X
X | M R E W Y
The transposition key:
CARGO
And the plaintext:
ATTACK AT ONCE
Step 1: Clean the plaintext
Remove spaces and punctuation, uppercase it, and merge J -> I if necessary.
ATTACKATONCE
Step 2: Convert each letter to coordinates
A -> AF
T -> AD
T -> AD
A -> AF
C -> GF
K -> DX
A -> AF
T -> AD
O -> DF
N -> FX
C -> GF
E -> XF
That gives the intermediate coordinate stream:
AFADADAFGFDXAFADDFFXGFXF
Step 3: Write it under the transposition key
C A R G O
---------
A F A D A
D A F G F
D X A F A
D D F F X
G F X F
Step 4: Reorder columns alphabetically by key
The letters of CARGO sort as:
A C G O R
So the columns are rearranged into that order.
Step 5: Read columns top to bottom
The final ciphertext becomes:
FAXDFADDDGDGFFFAFAXAFAFX
Grouped into blocks:
FAXDF ADDDG DGFFF AFAXA FAFX
Step 6: Decryption reverses the pipeline
To decrypt, you:
- remove grouping spaces
- determine the transposition grid dimensions
- rebuild the columns in sorted-key order
- restore the original column order
- read row by row to recover the coordinate stream
- split into pairs
- map each pair back through the square
Once you understand that pipeline, the cipher stops seeming mysterious. It becomes mechanical, which is exactly why it could be used in war.
ADFGVX: The 6x6 Version
By June 1918, the Germans introduced ADFGVX, an expanded form of the same idea.
The design did not fundamentally change. The square simply grew from 5x5 to 6x6, which made room for:
- all 26 letters
- all 10 digits
That matters more than it may first seem. Real military traffic is full of numbers: dates, quantities, coordinates, times, unit identifiers, train references, and ammunition counts. In ADFGX, numbers were awkward because the square only held 25 characters. In ADFGVX, they could be carried directly.
A simple example square might look like this:
A D F G V X
A | A B C D E F
D | G H I J K L
F | M N O P Q R
G | S T U V W X
V | Y Z 0 1 2 3
X | 4 5 6 7 8 9
Again, this is just a demonstration square. In practice, the contents would be keyed or mixed.
Now the system can directly encode messages such as:
ATTACK AT 1200
TRAIN 42
SECTOR 7
B2
27TH UNIT
without needing awkward external conventions for numbers.
That is the main practical advantage of ADFGVX. It is not a different species of cipher so much as the more fully militarised version of the same one.
Worked Example: ADFGVX Step by Step
Now the 6x6 version.
We will use the square:
A D F G V X
A | A B C D E F
D | G H I J K L
F | M N O P Q R
G | S T U V W X
V | Y Z 0 1 2 3
X | 4 5 6 7 8 9
The transposition key:
CARGO
And the plaintext:
ATTACK AT 1200
Step 1: Clean the plaintext
Remove spaces and punctuation:
ATTACKAT1200
Step 2: Convert each character to coordinates
Using the square above:
A -> AA
T -> GD
T -> GD
A -> AA
C -> AF
K -> DV
A -> AA
T -> GD
1 -> VG
2 -> VV
0 -> VF
0 -> VF
That produces the intermediate stream:
AAGDGDAAAFDVAAGDVGVVVFVF
Step 3: Write it under the transposition key
C A R G O
---------
A A G D G
D A A A F
D V A A G
D V G V V
V F V F
Step 4: Reorder columns alphabetically by key
Again, CARGO sorts to:
A C G O R
Step 5: Read columns top to bottom
The final ciphertext becomes:
AAVVFADDDVDAAVFGFGVGAAGV
Grouped into blocks:
AAVVF ADDDV DAAVF GFGVG AAGV
The point of the example is not that this exact square is historical. It is that the mechanism is the same, but now letters and digits live inside one unified system.
Fractionation: The Trick That Makes These Ciphers Feel Modern
This is the feature that makes ADFGX and ADFGVX genuinely instructive rather than merely old.
Most weak classical ciphers preserve locality. A letter becomes another letter, but it remains a visible unit. A digraph becomes another digraph, but it still survives as a recognisable adjacent block.
These ciphers behave differently.
A plaintext character becomes two coordinates. Those coordinates are then pushed into a transposition stage that separates them.
That means the statistics of the plaintext do not survive in one clean layer.
This is not modern diffusion in the full formal sense. But it is moving in that direction. It reveals a sharper design instinct than you find in simpler substitutions:
if the enemy analyst relies on obvious adjacency and repeated visible units, then destroy adjacency wherever possible.
That is what gives these ciphers their bite.
They are still breakable. They are still classical. But their design instinct is more ambitious than simple letter-for-letter disguise.
Why They Were Stronger Than They Looked
If you only looked at the final ciphertext, either cipher might seem almost underwhelming.
Only a handful of symbols. Endless repetition. Tight blocks of apparently simple output.
But that impression is misleading.
Their strength came from composition.
The square stage alone would still be vulnerable to systematic study. The transposition stage alone would also be vulnerable. The power comes from combining them in the right order.
Together, these ciphers do three important things:
- they disguise each plaintext character as a pair
- they double the apparent length of the message
- they scramble pair boundaries so the original units are harder to reconstruct
That does not make them unbeatable. But it makes them far more awkward than an ordinary monoalphabetic substitution.
The attacker has to untangle both the square and the transposition, while the fractionation step ensures that neither problem appears in a clean, isolated form.
Why the Germans Thought They Were Safe
From the German point of view, confidence in these systems was not irrational.
They were:
- more sophisticated than simple field substitutions
- practical to use by hand
- suitable for radio conditions
- more compact than some codebook-heavy alternatives
- genuinely harder to analyse than older textbook systems
That matters. These were not toy ciphers wrapped in legend. They were real wartime systems that substantially raised the cost of analysis.
But ciphers do not face abstract mathematics alone. They face:
- repeated traffic
- repeated keys
- stereotyped phrasing
- administrative regularity
- message volumes large enough for comparison
- operator mistakes
- skilled enemies with patience and urgency
That is where these systems met their limits.
Georges Painvin and the Breaking of the Cipher
The great name on the other side of the story is Georges Painvin, the French cryptanalyst associated with breaking ADFGX and ADFGVX under intense wartime pressure.
Painvin did not defeat the system because it was childish. He defeated it because real cryptanalysis is rarely about one sudden flash over one small sample. It is about traffic, structure, repetition, statistics, operator habits, and relentless work.
The broad picture is consistent even where retellings differ in detail:
- repeated or stereotyped message forms mattered
- traffic volume mattered
- the relation between message lengths and key structure mattered
- statistical attack mattered enormously
- the work was exhausting enough to become legendary
That last point survives in many tellings because it feels symbolically right. The cipher did not yield casually. It had to be wrestled down.
That is part of why it remains famous. It was strong enough to be worth the effort of breaking.
Did Breaking It Change the War?
This is where popular history often becomes too neat.
There is a long-standing claim that French success against ADFGX or ADFGVX provided decisive warning of a major German offensive and materially helped the Allies stop it. There is real substance behind the broader importance of the decrypts. But the simplified version, one cipher broken, one war direction changed, is too tidy.
The better view is the less theatrical one.
These decrypts were part of a wider intelligence struggle in which:
- codebreaking
- interpretation
- troop deployment
- command decisions
- operational planning
- broader intelligence context
all interacted.
That does not make the ciphers less important. It places them where they belong: as serious components in a much larger contest between secure communication and intelligence exploitation.
That version is less dramatic, but more truthful.
What Makes ADFGX and ADFGVX Weak by Modern Standards
Today, both systems are considered insecure.
That is no embarrassment. Nearly all historical hand ciphers are insecure by modern standards. The interesting question is why.
The square stage is still structured
Once the transposition is undone, the cipher collapses into a coordinate substitution system.
The transposition stage is only a single columnar transposition
Single transpositions are troublesome, but not deep enough to resist sustained attack indefinitely.
Real traffic leaks patterns
Repeated formats, repeated language, familiar openings, and administrative habits all create footholds.
Key reuse is dangerous
If multiple messages share the same daily settings, comparisons become much easier.
Human systems generate exploitable irregularities
Message lengths, mistakes, padding behaviour, and field conditions all produce clues.
In short, ADFGX and ADFGVX are clever, but not deep enough to withstand determined large-scale cryptanalysis once enough traffic is available.
Why They Still Matter
So why study them today?
Because they teach several important things unusually clearly.
They show how powerful composition can be
A substitution plus a transposition is far more interesting than either one alone.
They show the importance of representation
The moment a plaintext character stops being a fixed visible unit and becomes a coordinate pair, the nature of the problem changes.
They show the gap between design and deployment
A cipher can be ingenious on paper and still vulnerable in real use.
They belong to the birth of modern communications secrecy
They sit at the point where interception became normal and secrecy had to become structural.
They are visually and intellectually satisfying
You can teach them, implement them, inspect them, and understand every stage.
That last point matters more than people admit. Some historical ciphers are important but not especially elegant. ADFGX and ADFGVX are both important and elegant.
Where You Still Encounter Them Today
These ciphers survive surprisingly well outside formal military history.
You still see them in:
- puzzle hunts
- escape rooms
- geocaching mystery caches
- classroom cryptography materials
- hobbyist codebreaking forums
- game puzzles
- CTF-style classical cipher challenges
That makes sense. They have everything a good historical puzzle system wants:
- memorable names
- grids
- keywords
- visible mechanics
- hidden structure
- a satisfying two-stage "aha" moment
They are difficult enough to be interesting, but concrete enough to be teachable.
Python Treatment: Building ADFGX in Code
Below is a straightforward Python implementation of the ADFGX cipher. It focuses on clarity rather than cleverness and follows the classic 5x5 form with J merged into I.
It includes:
- square construction
- encryption
- decryption
- square printing
- grouped output formatting
- a worked example
Python Code
Python exampleShow code
import re
from math import ceil
from typing import Dict, List, Tuple
HEADERS = "ADFGX"
ALPHABET = "ABCDEFGHIKLMNOPQRSTUVWXYZ" # J merged into I
def normalize_square_key(key: str) -> str:
"""
Build the keyed 25-letter alphabet for the ADFGX square.
Keeps letters only, uppercases, merges J->I, removes duplicates,
then appends the rest of the alphabet.
"""
key = re.sub(r"[^A-Za-z]", "", key.upper()).replace("J", "I")
seen = set()
result = []
for ch in key:
if ch in ALPHABET and ch not in seen:
seen.add(ch)
result.append(ch)
for ch in ALPHABET:
if ch not in seen:
seen.add(ch)
result.append(ch)
return "".join(result)
def build_square(square_key: str) -> Tuple[List[List[str]], Dict[str, str], Dict[str, str]]:
"""
Returns:
square: 5x5 list of lists
char_to_pair: maps plaintext letter -> ADFGX pair
pair_to_char: maps pair -> plaintext letter
"""
keyed_alphabet = normalize_square_key(square_key)
square = []
char_to_pair = {}
pair_to_char = {}
idx = 0
for r in range(5):
row = []
for c in range(5):
ch = keyed_alphabet[idx]
row.append(ch)
pair = HEADERS[r] + HEADERS[c]
char_to_pair[ch] = pair
pair_to_char[pair] = ch
idx += 1
square.append(row)
return square, char_to_pair, pair_to_char
def print_square(square_key: str) -> None:
"""
Pretty-print the keyed Polybius square.
"""
square, _, _ = build_square(square_key)
print(" " + " ".join(HEADERS))
for r, row_label in enumerate(HEADERS):
print(f"{row_label} " + " ".join(square[r]))
def normalize_plaintext(text: str) -> str:
"""
Keep letters only, uppercase, merge J->I.
"""
return re.sub(r"[^A-Za-z]", "", text.upper()).replace("J", "I")
def normalize_ciphertext(text: str) -> str:
"""
Keep only A, D, F, G, X characters for decrypting.
"""
text = text.upper()
return "".join(ch for ch in text if ch in HEADERS)
def transposition_order(key: str) -> List[int]:
"""
Return column indices in alphabetical order of key letters.
Stable for duplicate letters: ties preserve left-to-right order.
"""
key = key.upper()
indexed = list(enumerate(key))
indexed.sort(key=lambda item: (item[1], item[0]))
return [i for i, _ in indexed]
def encrypt_adfgx(plaintext: str, square_key: str, transposition_key: str) -> str:
"""
Encrypt plaintext with the ADFGX cipher.
"""
if not transposition_key:
raise ValueError("Transposition key must not be empty.")
_, char_to_pair, _ = build_square(square_key)
cleaned = normalize_plaintext(plaintext)
fractionated = "".join(char_to_pair[ch] for ch in cleaned)
key_len = len(transposition_key)
rows = [fractionated[i:i + key_len] for i in range(0, len(fractionated), key_len)]
order = transposition_order(transposition_key)
ciphertext_parts = []
for col_idx in order:
for row in rows:
if col_idx < len(row):
ciphertext_parts.append(row[col_idx])
return "".join(ciphertext_parts)
def decrypt_adfgx(ciphertext: str, square_key: str, transposition_key: str) -> str:
"""
Decrypt ADFGX ciphertext.
"""
if not transposition_key:
raise ValueError("Transposition key must not be empty.")
_, _, pair_to_char = build_square(square_key)
cleaned = normalize_ciphertext(ciphertext)
key_len = len(transposition_key)
text_len = len(cleaned)
if text_len == 0:
return ""
num_rows = ceil(text_len / key_len)
remainder = text_len % key_len
original_lengths = []
for i in range(key_len):
if remainder == 0 or i < remainder:
original_lengths.append(num_rows)
else:
original_lengths.append(num_rows - 1)
order = transposition_order(transposition_key)
columns = [""] * key_len
pos = 0
for original_col_idx in order:
col_len = original_lengths[original_col_idx]
columns[original_col_idx] = cleaned[pos:pos + col_len]
pos += col_len
fractionated_chars = []
for r in range(num_rows):
for c in range(key_len):
if r < len(columns[c]):
fractionated_chars.append(columns[c][r])
fractionated = "".join(fractionated_chars)
if len(fractionated) % 2 != 0:
raise ValueError("Recovered fractionated text has odd length; ciphertext/key mismatch.")
plaintext_chars = []
for i in range(0, len(fractionated), 2):
pair = fractionated[i:i + 2]
if pair not in pair_to_char:
raise ValueError(f"Invalid pair during decryption: {pair}")
plaintext_chars.append(pair_to_char[pair])
return "".join(plaintext_chars)
def group_text(text: str, size: int = 5) -> str:
"""
Format text into blocks for display.
"""
return " ".join(text[i:i + size] for i in range(0, len(text), size))
if __name__ == "__main__":
# This keyed alphabet matches the worked example square used in the article.
square_key = "BTALPDHOZKQFVSNGICUXMREWY"
transposition_key = "CARGO"
plaintext = "ATTACK AT ONCE"
print("ADFGX keyed square:\n")
print_square(square_key)
ciphertext = encrypt_adfgx(plaintext, square_key, transposition_key)
decrypted = decrypt_adfgx(ciphertext, square_key, transposition_key)
print("\nPlaintext: ", plaintext)
print("Ciphertext:", group_text(ciphertext))
print("Decrypted: ", decrypted)
How the ADFGX Implementation Works
Here are the main parts of the implementation.
normalize_square_key()
This builds the keyed 25-letter alphabet for the ADFGX square.
If the key is a word or phrase, it:
- removes non-letters
- uppercases everything
- merges
J -> I - removes duplicates
- appends the rest of the unused alphabet
That produces the mixed alphabet written into the 5x5 square.
build_square()
This constructs:
- the 5x5 square itself
- a mapping from plaintext character to coordinate pair
- a reverse mapping from coordinate pair back to plaintext
So if C lands at row G, column F, then:
char_to_pair["C"] = "GF"
pair_to_char["GF"] = "C"
That makes encryption and decryption straightforward.
encrypt_adfgx()
Encryption happens in two stages.
First, the plaintext is normalised so that only letters remain and J is merged into I.
Then each letter is replaced by an ADFGX pair such as AF, DX, or GF. That creates the intermediate fractionated stream.
Next, that stream is written row by row under the transposition key. The columns are then read out in alphabetical order of the key letters.
The ordering is stable for duplicate letters, meaning ties are resolved left to right.
decrypt_adfgx()
Decryption reverses the entire process.
It:
- cleans the ciphertext to keep only
A D F G X - calculates the transposition grid dimensions
- works out how long each original column must be
- fills those columns in sorted-key order
- reconstructs the row-wise fractionated stream
- splits it into pairs
- converts each pair back through the square
The fiddliest part is reconstructing the columns correctly when the final transposition row is incomplete.
print_square() and group_text()
These are helper functions.
print_square()displays the keyed square clearlygroup_text()formats ciphertext into five-character blocks for easier reading
Example Run
Using:
square_key = "BTALPDHOZKQFVSNGICUXMREWY"
transposition_key = "CARGO"
plaintext = "ATTACK AT ONCE"
the script will:
- print the keyed square
- encrypt the plaintext
- decrypt the ciphertext back again
And the recovered plaintext will be:
ATTACKATONCE
A Few Practical Notes
J and I
Classic ADFGX normally merges J into I.
So:
JULIUS
becomes:
IULIUS
before encryption.
That means decryption cannot distinguish whether the original text used I or J. Context has to decide.
Spaces and punctuation
Traditional implementations remove them before encryption. This code does the same.
Duplicate letters in transposition keys
Some hand-worked examples avoid repeated letters because they are easier to explain. But real code should still handle them correctly. This implementation does so with stable left-to-right ordering for duplicates.
Ciphertext grouping
Historical ciphertext was often written in five-letter groups for readability. The group_text() helper provides that display format.
Python Treatment: Building ADFGVX in Code
Below is a straightforward Python implementation of the ADFGVX cipher. It follows the same overall pipeline as ADFGX, but uses a 6x6 square so it can carry letters and digits directly.
It includes:
- square construction
- encryption
- decryption
- square printing
- grouped output formatting
- a worked example
Python Code
Python exampleShow code
import re
from math import ceil
from typing import Dict, List, Tuple
HEADERS = "ADFGVX"
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def normalize_square_key(key: str) -> str:
"""
Build the keyed 36-character alphabet for the ADFGVX square.
Keeps letters and digits only, uppercases, removes duplicates,
then appends the rest of the alphabet.
If the caller passes a full 36-character mixed alphabet, it is preserved.
"""
cleaned = re.sub(r"[^A-Za-z0-9]", "", key.upper())
seen = set()
result = []
for ch in cleaned:
if ch in ALPHABET and ch not in seen:
seen.add(ch)
result.append(ch)
for ch in ALPHABET:
if ch not in seen:
seen.add(ch)
result.append(ch)
return "".join(result)
def build_square(square_key: str) -> Tuple[List[List[str]], Dict[str, str], Dict[str, str]]:
"""
Returns:
square: 6x6 list of lists
char_to_pair: maps plaintext char -> ADFGVX pair
pair_to_char: maps pair -> plaintext char
"""
keyed_alphabet = normalize_square_key(square_key)
square = []
char_to_pair = {}
pair_to_char = {}
idx = 0
for r in range(6):
row = []
for c in range(6):
ch = keyed_alphabet[idx]
row.append(ch)
pair = HEADERS[r] + HEADERS[c]
char_to_pair[ch] = pair
pair_to_char[pair] = ch
idx += 1
square.append(row)
return square, char_to_pair, pair_to_char
def print_square(square_key: str) -> None:
"""
Pretty-print the keyed 6x6 square.
"""
square, _, _ = build_square(square_key)
print(" " + " ".join(HEADERS))
for r, row_label in enumerate(HEADERS):
print(f"{row_label} " + " ".join(square[r]))
def normalize_plaintext(text: str) -> str:
"""
Keep only letters and digits, uppercase everything.
"""
return re.sub(r"[^A-Za-z0-9]", "", text.upper())
def normalize_ciphertext(text: str) -> str:
"""
Keep only A, D, F, G, V, X characters for decrypting.
"""
text = text.upper()
return "".join(ch for ch in text if ch in HEADERS)
def transposition_order(key: str) -> List[int]:
"""
Return column indices in alphabetical order of key letters.
Stable for duplicate letters: ties preserve left-to-right order.
"""
key = key.upper()
indexed = list(enumerate(key))
indexed.sort(key=lambda item: (item[1], item[0]))
return [i for i, _ in indexed]
def encrypt_adfgvx(plaintext: str, square_key: str, transposition_key: str) -> str:
"""
Encrypt plaintext with the ADFGVX cipher.
"""
if not transposition_key:
raise ValueError("Transposition key must not be empty.")
_, char_to_pair, _ = build_square(square_key)
cleaned = normalize_plaintext(plaintext)
fractionated = "".join(char_to_pair[ch] for ch in cleaned)
key_len = len(transposition_key)
rows = [fractionated[i:i + key_len] for i in range(0, len(fractionated), key_len)]
order = transposition_order(transposition_key)
ciphertext_parts = []
for col_idx in order:
for row in rows:
if col_idx < len(row):
ciphertext_parts.append(row[col_idx])
return "".join(ciphertext_parts)
def decrypt_adfgvx(ciphertext: str, square_key: str, transposition_key: str) -> str:
"""
Decrypt ADFGVX ciphertext.
"""
if not transposition_key:
raise ValueError("Transposition key must not be empty.")
_, _, pair_to_char = build_square(square_key)
cleaned = normalize_ciphertext(ciphertext)
key_len = len(transposition_key)
text_len = len(cleaned)
if text_len == 0:
return ""
num_rows = ceil(text_len / key_len)
remainder = text_len % key_len
original_lengths = []
for i in range(key_len):
if remainder == 0 or i < remainder:
original_lengths.append(num_rows)
else:
original_lengths.append(num_rows - 1)
order = transposition_order(transposition_key)
columns = [""] * key_len
pos = 0
for original_col_idx in order:
col_len = original_lengths[original_col_idx]
columns[original_col_idx] = cleaned[pos:pos + col_len]
pos += col_len
fractionated_chars = []
for r in range(num_rows):
for c in range(key_len):
if r < len(columns[c]):
fractionated_chars.append(columns[c][r])
fractionated = "".join(fractionated_chars)
if len(fractionated) % 2 != 0:
raise ValueError("Recovered fractionated text has odd length; ciphertext/key mismatch.")
plaintext_chars = []
for i in range(0, len(fractionated), 2):
pair = fractionated[i:i + 2]
if pair not in pair_to_char:
raise ValueError(f"Invalid pair during decryption: {pair}")
plaintext_chars.append(pair_to_char[pair])
return "".join(plaintext_chars)
def group_text(text: str, size: int = 5) -> str:
"""
Format text into blocks for display.
"""
return " ".join(text[i:i + size] for i in range(0, len(text), size))
if __name__ == "__main__":
# This demonstration uses a simple alphabetic 6x6 square for clarity.
square_key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
transposition_key = "CARGO"
plaintext = "ATTACK AT 1200"
print("ADFGVX keyed square:\n")
print_square(square_key)
ciphertext = encrypt_adfgvx(plaintext, square_key, transposition_key)
decrypted = decrypt_adfgvx(ciphertext, square_key, transposition_key)
print("\nPlaintext: ", plaintext)
print("Ciphertext:", group_text(ciphertext))
print("Decrypted: ", decrypted)
How the ADFGVX Implementation Works
The ADFGVX version follows exactly the same overall logic as ADFGX, but the square is larger and the plaintext alphabet is broader.
normalize_square_key()
This builds the keyed 36-character alphabet for the 6x6 square.
It:
- removes non-alphanumeric characters
- uppercases everything
- removes duplicates
- appends the remaining unused characters from
A-Zand0-9
If the user already supplies a full 36-character mixed alphabet, that structure is preserved.
build_square()
This creates:
- the 6x6 square
- a mapping from plaintext character to coordinate pair
- a reverse mapping from pair to character
So if 7 lands at row G, column V, then:
char_to_pair["7"] = "GV"
pair_to_char["GV"] = "7"
That lets the system handle letters and numbers inside one unified mechanism.
encrypt_adfgvx()
Encryption again happens in two stages.
First, the plaintext is normalised so that only letters and digits remain.
Then each character becomes a coordinate pair drawn from A D F G V X.
That produces the intermediate fractionated stream.
Next, the fractionated stream is written row by row under the transposition key, and the columns are read out in sorted-key order.
decrypt_adfgvx()
Decryption is the reverse pipeline:
- clean the ciphertext
- determine the transposition grid size
- reconstruct column lengths
- rebuild the original column order
- read row by row to recover the fractionated stream
- split into pairs
- map each pair back through the square
As with ADFGX, the trickiest part is correctly reconstructing the uneven columns when the last row is incomplete.
Example Run
Using:
square_key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
transposition_key = "CARGO"
plaintext = "ATTACK AT 1200"
the script will:
- print the 6x6 square
- encrypt the plaintext
- decrypt it back again
And the recovered plaintext will be:
ATTACKAT1200
A Few Practical Notes
Letters and digits
Unlike ADFGX, ADFGVX can carry:
- all 26 letters
- all 10 digits
That is one reason it was more practical for genuine military communications.
Spaces and punctuation
Traditional implementations normally remove them before encryption. This code does the same.
Duplicate letters in transposition keys
As with the ADFGX code, repeated letters in the transposition key are handled safely by stable left-to-right ordering.
Grouped output
The same group_text() helper formats the ciphertext into five-character groups for readability.
Related Ciphers
ADFGX and ADFGVX make the most sense when placed beside their neighbours.
- ADFGX Cipher is the earlier 5x5 wartime version built for letters only.
- ADFGVX Cipher is the expanded 6x6 version that carries both letters and digits.
- Polybius Square Cipher explains the coordinate-grid idea underneath the first stage.
- Columnar Transposition Cipher explains the keyword-based shuffle used in the second stage.
- Bifid Cipher is another fractionating design that remixes coordinates rather than simply replacing letters.
Together, ADFGX and ADFGVX show the same wartime idea in two forms: one built for alphabetic traffic, and one expanded to cope with the numerical demands of real military communication.