Engee documentation
Notebook

The ADFGVX cipher

This example examines the implementation of the ADFGVX cipher, one of the most complex military ciphers of the First World War used by the German army.

Introduction

ADFGVX is a replacement polygraphic cipher used by Germany during the First World War. It combines a substitution cipher (using the Polybius table) and a transposition cipher. The encryption algorithm was designed for a high level of security, using a limited set of letters for encoding (A, D, F, G, V, X), which facilitated radio transmission. This cipher became known for being considered unsolvable for a long time, and was successfully cracked only by the French cryptographer Georges Penven.

In [ ]:
# Install the required package, if it is not already installed.
import Pkg; Pkg.add("Random")

# We connect a package for generating random numbers and shuffling
using Random
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

The main part

The data structure for the ADFGVX cipher

First of all, we will define the data structure for storing all the necessary cipher components.:

In [ ]:
"""
    struct ADFGVX

Defines the structure for storing ADFGVX cipher parameters:

- `polybius': symbol table (encrypted alphabet)
- `pdim`: Polybius table size (e.g. 6 for 6x6)
- `key`: the key of the transposition
- `keylen': key length
- `alphabet`: алфавит, используемый для кодирования (обычно "ADFGVX")
- `encode': encryption dictionary (character -> letter pair)
- `decode': decryption dictionary (letter pair -> symbol)
"""
struct ADFGVX
    polybius::Vector{Char}
    pdim::Int
    key::Vector{Char}
    keylen::Int
    alphabet::Vector{Char}
    encode::Dict{Char, Vector{Char}}
    decode::Dict{Vector{Char}, Char}
end
Out[0]:
ADFGVX

ADFGVX Structure Constructor

Next, we create a constructor that generates a mapping table and fills in encryption and decryption dictionaries.:

In [ ]:
"""
    ADFGVX(s, k, alph = "ADFGVX")

The constructor creates an ADFGVX cipher object based on the data:
- `s`: a string of characters forming the Polybius table
- `k': the transposition key
- `alph`: алфавит шифра, по умолчанию "ADFGVX"
"""
function ADFGVX(s, k, alph = "ADFGVX")
    # Convert it to a vector of uppercase letters
    poly = collect(uppercase(s))
    pdim = isqrt(length(poly))  # calculating the dimension of the table (for a square one)
    
    al = collect(uppercase(alph)) # converting the alphabet
    
    # Creating an encryption dictionary: a pair of letters is assigned to each character.
    enco::Dict = Dict([(poly[(i - 1) * pdim + j] => [al[i], al[j]])
        for i in 1:pdim, j in 1:pdim])
    
    # Creating a dictionary for decoding (reverse)
    deco = Dict(last(p) => first(p) for p in enco)
    
    # Checking the correctness of the data
    @assert pdim^2 == length(poly) && pdim == length(al)
    
    # Returning the initialized object
    return ADFGVX(poly, pdim, collect(uppercase(k)), length(k), al, enco, deco)
end
Out[0]:
ADFGVX

Encryption function

The encryption function converts the entered text into a sequence of characters from the cipher alphabet, then applies a key-based transposition.

In [ ]:
"""
    encrypt(s::String, k::ADFGVX)

Encrypts the string `s` using an object `k` of type `ADFGVX'.
"""
function encrypt(s::String, k::ADFGVX)
    # Characters to be encrypted
    chars = reduce(vcat, [k.encode[c] for c in 
        filter(c -> c in k.polybius, collect(uppercase(s)))])

    # We distribute the symbols into columns according to the key
    colvecs = [lett => chars[i:k.keylen:length(chars)] for (i, lett) in enumerate(k.key)]
    
    # Arrange the columns alphabetically.
    sort!(colvecs, lt = (x, y) -> first(x) < first(y))
    
    # Combining the sorted columns into a row
    return String(mapreduce(p -> last(p), vcat, colvecs))
end
Out[0]:
encrypt

Decryption function

In [ ]:
"""
    decrypt(s::String, k::ADFGVX)

Decrypts the string `s` encrypted with the ADFGVX cipher.
"""
function decrypt(s::String, k::ADFGVX)
    # Filtering the characters included in the cipher alphabet
    chars = filter(c -> c in k.alphabet, collect(uppercase(s)))
    sortedkey = sort(collect(k.key)) # sorting the key
    
    # Sorting order for columns
    order = [findfirst(c -> c == ch, k.key) for ch in sortedkey]
    originalorder = [findfirst(c -> c == ch, sortedkey) for ch in k.key]
    
    # Calculating column lengths (taking into account possible uneven distribution)
    a, b = divrem(length(chars), k.keylen)
    strides = [a + (b >= i ? 1 : 0) for i in order]
    starts = accumulate(+, strides[begin:end-1], init=1)
    pushfirst!(starts, 1)
    ends = [starts[i] + strides[i] - 1 for i in 1:k.keylen]
    
    # Restoring the columns to their original order
    cols = [chars[starts[i]:ends[i]] for i in originalorder]
    
    # Restoring rows from columns
    pairs, nrows = Char[], (length(chars) - 1) ÷ k.keylen + 1
    for i in 1:nrows, j in 1:k.keylen
        (i - 1) * k.keylen + j > length(chars) && break
        push!(pairs, cols[j][i])
    end
    
    # Converting pairs of characters back to the characters of the original alphabet
    return String([k.decode[[pairs[i], pairs[i + 1]]] for i in 1:2:length(pairs)-1])
end
Out[0]:
decrypt

Demonstration of the work

Generating a random Polybius table and a random key

In [ ]:
const POLYBIUS = String(shuffle(collect("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")))
Out[0]:
"IJQBWNR9MFS17GD85L2UAC4TVK6OZEH3X0PY"

Download the English dictionary and select a random matching key of length 9

In [ ]:
# Download the English dictionary and select a random matching key of length 9
const KEY = read("unixdict.txt", String) |>
    v -> split(v, r"\s+") |>
    v -> filter(w -> (n = length(w); n == 9 && n == length(unique(collect(w)))), v) |>
    shuffle |> first |> uppercase
Out[0]:
"KINGSBURY"

Creating a cipher object

In [ ]:
const SECRETS = ADFGVX(POLYBIUS, KEY)
message = "ATTACKAT1200AM"
Out[0]:
"ATTACKAT1200AM"
In [ ]:
# Data output to the screen
println("Polybius: $POLYBIUS, Key: $KEY\n")
println("Message: $message\n")

# Message encryption
encoded = encrypt(message, SECRETS)
println("Encoded: $encoded\n")

# Decrypting the message
decoded = decrypt(encoded, SECRETS)
println("Decoded: $decoded\n")
Polybius: IJQBWNR9MFS17GD85L2UAC4TVK6OZEH3X0PY, Key: KINGSBURY

Message: ATTACKAT1200AM

Encoded: XGGXGGFVAGGGFGDXFDFGFXGXGGXD

Decoded: ATTACKAT1200AM

Conclusion

We have reviewed and implemented the ADFGVX cipher in Julia, one of the most secure field ciphers of the First World War. We have learned:

  • build an encryption table (Polybius table);

  • create substitution rules (encryption by pairs of characters);

  • use key-based transposition;

  • Restore the original message at the decryption stage.

This project demonstrates the importance of studying classical cryptographic methods that underlie modern data protection systems. The Julia implementation makes it easy to experiment with algorithms and understand how they work "under the hood".

The example was developed using materials from Rosetta Code