Lorenz Cipher

Work in progress. This note is still being written and incomplete.

A classical, poly-alphabetic, stream cipher. Encrypts a Baudot-coded plaintext bit stream into 5 ciphertext bit streams. Each bit is masked by a fresh keystream bit, so ciphertext symbol frequencies are flattened.

Implemented by the Lorenz machine, an electromechanical inline cipher attachment built by C. Lorenz AG for the German Army high command in World War 2. It sat between a teleprinter and the transmission line, enciphering each character as it was typed and deciphering at the far end. The Allies called the machine and its traffic Tunny.

Baudot Code

A fixed-width (5 bits) character encoding, invented by Émile Baudot in 1870 for printing telegraphs.

Fixed width is the key property. The receiver slices the bit stream every 5 bits to find character boundaries, unlike the variable-length symbols of Morse code. This is why the Lorenz machine treats plaintext as a clean stream of 5-bit units.

A 5-bit code spans only 25=322^5 = 32 symbols, too few for letters, digits, punctuation, and control codes. 2 of the 32 codes are mode switches rather than characters.

  • Letters shift
    Following codes are read as letters.
  • Figures shift
    Following codes are read as digits and symbols.

The machine holds the current mode. The same 5-bit pattern means different characters in the 2 modes, so the effective alphabet is about 60 symbols.

Encryption

Each 5-bit Baudot code is XOR-ed with a 5-bit random sequence, generated by 12 shift registers in 3 groups:

  • 5 χ\chi registers, lengths 41, 31, 29, 26, 23.
  • 5 ψ\psi registers, lengths 43, 47, 51, 53, 59.
  • 2 μ\mu registers, lengths 61, 37.

Registers

A register is a wheel of pins, 1 per position around its length, each fixed to 00 or 11. At each tick it outputs the bit at its current position, then advances by 1 position, wrapping at its length.

χi\chi_i pairs with ψi\psi_i for each of the 5 Baudot bit positions, giving 5 parallel lanes.

At each clock tick tt:

  • K=(χiψi)K = (\chi_i \oplus \psi_i) for i=1,,5i = 1, \dots, 5.
  • tt+1t \leftarrow t + 1, and the χ\chi registers advance.
  • μ(1)\mu^{(1)} advances every tick, and conditionally triggers μ(2)\mu^{(2)} to advance.
  • μ(2)\mu^{(2)} conditionally triggers the ψ\psi registers to advance.
  • Output KK.

Key space:

241+31+29+26+23+43+47+51+53+59+61+37=25012^{41+31+29+26+23+43+47+51+53+59+61+37} = 2^{501}

Decryption

The receiver starts the 12 registers from the shared settings and runs the same clocking, producing the identical keystream KK. XOR is its own inverse, so XOR-ing each 5-bit ciphertext code with KK returns the plaintext code.

Attacking

The keystream is fully determined once the register settings are fixed, so 2 messages sent in depth, under the same settings, break the cipher.

  • XOR the 2 ciphertexts. The keystream cancels, leaving P1P2P_1 \oplus P_2, the XOR of the 2 plaintexts.
  • Slide known language fragments against P1P2P_1 \oplus P_2 to separate the 2 plaintexts, then XOR either plaintext with its ciphertext to recover the keystream.
  • The ψ\psi registers stall on many ticks, so the keystream carries χ\chi structure. Statistical tests on the ciphertext bits expose the χ\chi register lengths and starting positions without a depth.

Worked Example

One Baudot character A, code 1100011000, with keystream bits 0110101101 for this tick.

Encryption XORs the code with the keystream bit by bit.

1100001101=1010111000 \oplus 01101 = 10101

Ciphertext code is 1010110101.

Decryption XORs the ciphertext with the same keystream bits.

1010101101=1100010101 \oplus 01101 = 11000

Recovered code is 1100011000, the character A.

For the attack, a second message encrypts code 0011000110 at this tick under the same settings, giving ciphertext 0011001101=0101100110 \oplus 01101 = 01011.

1010101011=11110=110000011010101 \oplus 01011 = 11110 = 11000 \oplus 00110

The keystream 0110101101 has cancelled, leaving the XOR of the 2 plaintext codes.

Written by September 16, 2026 4 min read
Was this helpful?