I wrote about the multiplication code used by the double agent Eddie Chapman, known as Agent Zigzag. By using a memorised keyword and the date, Chapman could produce a unique numerical shift key for his cipher every day, leaving no physical evidence for counter-intelligence to find.
To show how this works in a modern context, I have translated this manual process into Python. The script replicates the original algorithm and includes functions to handle the keyword, the daily shift, and the encryption process.
The code
def process_keyword(keyword):
"""
Step 1: Assigns numerical values to the keyword based on alphabetical order.
Returns the combined integer.
"""
# Sanitize keyword to uppercase letters only
keyword = "".join(c for c in keyword.upper() if c.isalpha())
# Create a list of tuples: (character, original_index)
indexed_chars = [(char, i) for i, char in enumerate(keyword)]
# Sort alphabetically. Python's sort is stable, meaning duplicates
# automatically stay in their original left-to-right order.
sorted_chars = sorted(indexed_chars)
# Reconstruct the sequence based on the original positions
base_seq = [0] * len(keyword)
for rank, (char, orig_index) in enumerate(sorted_chars, start=1):
base_seq[orig_index] = str(rank)
return int("".join(base_seq))
def generate_shift_key(keyword, day):
"""
Step 2: Multiplies the base sequence by the day of the month.
"""
base_int = process_keyword(keyword)
shift_key_int = base_int * day
return str(shift_key_int)
def agent_zigzag_encrypt(keyword, day, message):
"""
Steps 3 & 4: Pads and encrypts the message using the generated shift key.
"""
shift_key_str = generate_shift_key(keyword, day)
# Sanitize and pad the message with 'X's to a multiple of 5
message = "".join(c for c in message.upper() if c.isalpha())
remainder = len(message) % 5
if remainder != 0:
message += 'X' * (5 - remainder)
ciphertext = ""
key_len = len(shift_key_str)
# Encrypt by shifting forward
for i, char in enumerate(message):
shift = int(shift_key_str[i % key_len])
# Calculate new character wrapping around the alphabet
new_char = chr(((ord(char) - ord('A') + shift) % 26) + ord('A'))
ciphertext += new_char
# Group into blocks of 5 for standard radio transmission
blocks = [ciphertext[i:i+5] for i in range(0, len(ciphertext), 5)]
return " ".join(blocks)
def agent_zigzag_decrypt(keyword, day, ciphertext):
"""
Decrypts the message by reversing the shift.
"""
shift_key_str = generate_shift_key(keyword, day)
# Remove any spaces from transmission blocks
ciphertext = ciphertext.replace(" ", "")
plaintext = ""
key_len = len(shift_key_str)
# Decrypt by shifting backward
for i, char in enumerate(ciphertext):
shift = int(shift_key_str[i % key_len])
# Calculate original character wrapping around the alphabet
orig_char = chr(((ord(char) - ord('A') - shift) % 26) + ord('A'))
plaintext += orig_char
return plaintext
if __name__ == "__main__":
keyword = "RADIO"
day = 15
message = "SEND HELP"
print("--- Multiplication code example ---")
print(f"Keyword: {keyword}")
print(f"Day: {day}")
print(f"Original message: {message}\n")
# 1. Show the generated shift key
shift_key = generate_shift_key(keyword, day)
print(f"Generated shift key: {shift_key}")
# 2. Encrypt
encrypted_message = agent_zigzag_encrypt(keyword, day, message)
print(f"Ciphertext: {encrypted_message}")
# 3. Decrypt
decrypted_message = agent_zigzag_decrypt(keyword, day, encrypted_message)
print(f"Decrypted message: {decrypted_message} (includes padding)")
How the code works
- Sorting with duplicates: The
process_keywordfunction pairs each letter with its original index before sorting. Since Python uses a stable sorting algorithm, duplicate letters are numbered from left to right as they appear in the word. - Alphabet wrapping: The line
chr(((ord(char) - ord('A') + shift) % 26) + ord('A'))manages the wrap-around. It converts a character to a 0–25 index, adds the shift, and uses the modulo operator to loop from ‘Z’ back to ‘A’. - Grouping for transmission: The script divides the output into blocks of five characters. This follows the Morse code format used by operatives in the field.