From Caesar to a Simplified Enigma
June 1, 2025
How does Enigma work? Let’s start with the basics.
Caesar Cipher
A Caesar cipher is one of the simplest and most widely known encryption techniques. It’s a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down or up the alphabet. For example, with a shift of 2, ‘a’ becomes ‘c’, ‘b’ becomes ‘d’, …, and ‘z’ becomes ‘b’.
This cipher can be mathematically described using modular arithmetic. If we map letters a-z to numbers 0-25, the encryption of a letter $x$ with a shift $k$ is $E(x) = (x + k) \pmod{26}$. This is also known as a cyclic group.
Caesar Cipher Visualized
Caesar Cipher Interactive Demo
Encrypted Text:
Substitution Cipher
A substitution cipher is a method of encrypting in which units of plaintext are replaced with ciphertext, according to a fixed system. In a simple monoalphabetic substitution cipher, each letter of the alphabet is mapped to a unique different letter. The “key” is this permutation of the alphabet. This is also known as a symmetry group.
Substitution Cipher Visualized
Substitution Cipher Interactive Demo
Standard Alphabet: abcdefghijklmnopqrstuvwxyz
Maps to Key:
Encrypted Text:
A Stepping Substitution Cipher (Polyalphabetic Idea)
To make ciphers stronger, we can change the substitution key as we encrypt. This is a basic idea behind polyalphabetic ciphers.
In this example, we start with an initial substitution key. After you encrypt a piece of text by pressing the “Encrypt and Step Key” button, the substitution key itself will be modified (shifted by one position, like a Caesar cipher with shift 1). The next time you encrypt text, it will use this new, stepped key.
Stepping Substitution Cipher Demo
phqgiumeaylnofdxjkrcvstzwbEncrypted Text (using current key):
Mapping Letters to Numbers for Ciphers
Many ciphers, especially more complex ones implemented mathematically or in computers, operate on numbers rather than directly on letters. To use such ciphers for text, we first need a consistent way to convert letters into numbers and then back again.
A common mapping for the English alphabet is:
- ‘a’ = 0
- ‘b’ = 1
- ‘c’ = 2
- …
- ‘z’ = 25
The total number of unique characters in our alphabet (26 in this case) is often denoted by n
. So, for English letters, n=26
. After encryption, the resulting numbers can be converted back to letters using the same mapping.
EasyEnigma
: A Simplified Rotor Cipher
Our EasyEnigma
, implemented in Rust, is a simplified model designed to illustrate its core operational principles. While simpler, it captures the essence of how rotors, a reflector, and stepping mechanisms work together to encrypt messages.
EasyEnigma
operates as follows:
-
Numerical Alphabet (
n
): The cipher operates on numbers, typically from0
ton-1
. The variablen
in theEasyEnigma
struct represents this alphabet size. For example, if we’re encrypting English letters, we might map ‘a’ to 0, ‘b’ to 1, …, ‘z’ to 25, makingn=26
. - Rotors (
rotor_wirings
,rotor_inv_wirings
): The machine features a configurable number of rotors. The wiring for each rotor is a permutation of then
possible inputs. This means each number from0
ton-1
is mapped to a unique number within the same range for that rotor.- In the
EasyEnigma
struct,rotor_wirings: Vec<Vec<u32>>
stores these permutations, oneVec<u32>
for each rotor. - These permutations are initially created by the
generate_permutation(n, rng)
function for each rotor when a newEasyEnigma
instance is made. - For the signal to travel backward through the rotors during encryption, the machine also stores their inverse permutations:
rotor_inv_wirings: Vec<Vec<u32>>
. These are calculated by theinvert_permutation(perm: &[u32])
function for each rotor’s wiring.
- In the
- Reflector (
reflector_wiring
): A single reflector is used after the signal has passed forward through the rotors. The reflector is also a permutation but has a special property: it’s an involution. This means if it maps a numberi
toj
, it must also mapj
back toi
. A crucial feature of historical Enigma reflectors (and aimed for ingenerate_reflector
for evenn
) is that no character maps to itself (no fixed points).- The
reflector_wiring: Vec<u32>
in the struct holds this configuration. - It’s generated by the
generate_reflector(n, rng)
function, which attempts to create random pairings.
- The
- Stepping Mechanism (
step
): To ensure that the same input character doesn’t always encrypt to the same output character (a key feature of polyalphabetic ciphers), the rotors “step” or rotate. This changes the effective wiring for each character encrypted.EasyEnigma
uses a list of counters,step: Vec<u32>
, stored in the struct, representing the current rotational offset of each rotor. These are typically initialized to0
for each rotor.- The stepping logic is found at the end of the
call_char
method. The first rotor (index 0) steps after every character. If it completes a full revolution (its step counter returns to 0), the next rotor (index 1) steps once. If that rotor also completes a revolution, the subsequent rotor steps, and so on, like an odometer:// Step for the next character if self.step.is_empty() { /* Or handle as an error if no rotors */ return; } self.step = (self.step + 1) % self.n; // First rotor always steps for i in 0..(self.step.len() - 1) { if self.step[i] == 0 { // If rotor 'i' completed a full revolution (just turned over to 0) self.step[i+1] = (self.step[i+1] + 1) % self.n; // Then rotor 'i+1' steps } else { // If rotor 'i' did not turn over, then subsequent rotors do not step from this cascade break; } }
This is a simpler “odometer” style stepping compared to the historical Enigma.
-
Encryption Path (within
call_char
method): When a single numberx
(representing a character) is encrypted using thecall_char(&mut self, x: u32) -> u32
method, it undergoes the following transformations:- Forward Pass through Rotors:
The input
x
passes sequentially through each rotor, from the first (index 0) to the last. The transformation through each rotor is handled by thepass_rotor
helper function.// Forward path let mut current_val = x; for i in 0..self.rotor_wirings.len() { // Iterate through each rotor current_val = Self::pass_rotor(current_val, i, self.step[i], true, self.n, &self.rotor_wirings, &self.rotor_inv_wirings); }
The
pass_rotor
function (fn pass_rotor(val: u32, rotor_idx: usize, step_val: u32, forward: bool, n: u32, wirings: &[Vec<Vec<u32>>], inv_wirings: &[Vec<Vec<u32>>]) -> u32
) works like this:effective_input = (val + step_val) % n;
: The input value is shifted by the rotor’s current step position. This simulates the rotation of the rotor relative to a fixed entry point.- If
forward
is true,wired_output = wirings[rotor_idx][effective_input as usize];
otherwise (for backward pass),wired_output = inv_wirings[rotor_idx][effective_input as usize];
. The shifted value then passes through the rotor’s actual fixed wiring (or its inverse). output = (wired_output + n - step_val) % n;
: The result is then shifted back by the rotor’s step value. The+ n
ensures the modulo arithmetic handles potential negative results correctly.
- Reflector:
The output from the last rotor then enters the
reflector_wiring
.// Reflector current_val = self.reflector_wiring[current_val as usize];
- Backward Pass through Rotors:
The reflected value then travels back through the rotors, but in reverse order (from the last rotor back to the first) and using their inverse wirings. The
pass_rotor
function is used again, but withforward
set tofalse
.// Backward path for i in (0..self.rotor_wirings.len()).rev() { // Iterate through rotors in reverse current_val = Self::pass_rotor(current_val, i, self.step[i], false, self.n, &self.rotor_wirings, &self.rotor_inv_wirings); }
The final
current_val
after this entire path is the encrypted number.
- Forward Pass through Rotors:
The input
- Initialization and Configuration:
An
EasyEnigma
instance is created using theEasyEnigma::new(n: u32, rng: &mut impl Rng) -> Self
constructor, which sets up the random rotor wirings (for all configured rotors) and reflector for a given alphabet sizen
. Users can also define specific configurations usingset_wirings(...)
or adjust the initial rotor positions usingset_steps(...)
. Thereset_steps()
method returns the rotors to their0
position.
My implementation is located here.
EasyEnigma
vs. Historical Enigma & Its Differences (More Succinct)
Historical Enigma Briefly:
Used 3-5 wired rotors, each performing a full alphabet substitution. Rotors stepped (rotated) in a complex, odometer-like fashion, changing the substitution for each character. A plugboard added an initial/final letter swap, and a reflector ensured self-reciprocity (if A encrypts to B, B encrypts to A, given the same machine state) and that no letter encrypted to itself.
Our EasyEnigma
aims to demonstrate the core ideas of a rotor cipher, but there are some differences to the historical Enigma machines used in WWII:
- Core Similarities: Both use rotating substitution units (rotors) that change with each character, a reflector, and a path for the signal through rotors, reflector, and back. This creates a polyalphabetic cipher where the substitution changes constantly.
- Key Differences:
- Complexity:
EasyEnigma
supports a configurable number of rotors and uses a simple odometer-style stepping. Historical Enigmas typically used 3-4 rotors (chosen from a larger set of available rotors, e.g., 5 or 8) with more complex, notched stepping mechanisms that allowed for more irregular movement. - Wirings:
EasyEnigma
uses randomly generated permutations for rotor and reflector wirings. Historical Enigma rotors and reflectors had specific, carefully designed wirings. - Plugboard (Steckerbrett): Most military Enigmas featured a plugboard that swapped pairs of letters before and after the rotor assembly, adding a massive layer of cryptographic strength.
EasyEnigma
omits this.
- Complexity:
In essence, EasyEnigma
provides a conceptual taste of rotor machine cryptography without the full operational and cryptographic complexity of its historical counterpart.
Resources
- Wikipedia: Enigma Machine
- CryptoHack - A fun platform to learn modern cryptography by solving challenges.
- Khan Academy: Journey into Cryptography