01 The messages
Weierud’s paper, German Police Doppelkastenschlüssel Messages, transcribes six messages and reproduces the teleprinter copies and W/T Red Forms from NARA, RG 457, Historic Cryptographic Collection, Box 1386 (NR. 4417, “German WWII Police and SS Traffic”) and Box 202 (NR. 976, “Study of German Police Traffic”). Each is labelled here by its Beaumanor serial (BMR A115, D33 and so on). The first five-letter group of each part is the discriminator, which names the key and is not part of the text; the letter counts below are without it.
| serial | date | route | letters | discriminator |
|---|---|---|---|---|
| A115 | 16 June 1942 | SQF Mogilev (HSSPF Russland Mitte) → ALQ via DQH Berlin | 223 | ARTTN |
| D33 | 27 Feb 1942 | SRS1 Rovno → DQH Berlin | 74 | KFWOJ |
| D34 | 27 Feb 1942 | SQF Mogilev → DSO, Kommandostab RFSS | 160 + 86 | DNOSX / DRANR |
| E72 | 27 Feb 1942 | SRS3 Proskurov → SRS1, SRS2 Brest | 178 + 144 | SBWYY / LENRA |
| D67 | 27 Feb 1942 | SRS3 Proskurov → SRS1, SRS2 | 170 + 158 | OWQWJ / WNSXO |
| C43 | dated 28 Feb 1943 | SRS Kiev → SRS1 | 138 | SRXOS |
The second part of C43 was lost to interference (“2TL SWAMPED QRM”). Two groups of E72 part 2 are partly illegible. Weierud warns that O and Q are hard to tell apart on the copies. In D67 the second group of line 2 is taken as ZAVGO, following the operator’s own check line (“CTN 2/6 ZAVGO”). The transcriptions used are in msgs.txt.
02 A115 and its Bletchley decrypt
A115 is Nr. 14 from Mogilev, callsign SQF, the station at the headquarters of the Higher SS and Police Leader Russia Centre, handed in at 2325 on 16 June 1942 by its preamble and taken down at Beaumanor on 3742 kHz; stations 51 and 43 both copied it. Hanyok’s Eavesdropping on Hell (NSA, 2005) follows this one message from intercept to decrypt, and reproduces Bletchley’s decrypt as entry 21 of that day’s German police decrypts:
An Rf SS und Chef Orpo. An Strasse BOBRUISK–MOGILEW, Partisanenkampf: 16 Mann von Pol. Batl. 51 gefallen. Das Dorf BORKI, in dem Waffen und Munition gefunden wurden, wurde dem Erdboden gleichgemacht. Die Einwohnerschaft liquidiert. Von Höheren SS und Pol.führer Russland Mitte.
To the Reichsführer SS and the Chief of the Order Police: in a fight with partisans on the Bobruisk–Mogilev road, sixteen men of Police Battalion 51 were killed; the village of Borki, where weapons and ammunition had been found, was levelled; the inhabitants were liquidated. From the Higher SS and Police Leader Russia Centre. Hanyok notes that several villages named Borki exist in present-day Belarus and does not locate this one. The decrypt is what makes this group of messages “partially solved” in Schmeh’s 2021 list of unsolved Second World War ciphers.
An odd count
The message is 228 letters in both intercepts and in the German preamble. Without the discriminator that leaves 223, but the cipher works on pairs and every enciphered text has an even length. The German clerk therefore added or dropped a letter, or a letter was lost in transmission. Where it went can be measured. A double-box cipher is deterministic on pairs, so a repeated plaintext pair in the same position of the layout gives a repeated cipher pair, and the pairs only show their repeats when they are counted from the right starting point. Counted from the end, A115 has 25 repeated digraphs of 19 types. Counted from the start it has 8, which is chance level. So the stray letter lies among the first nine or so, and the rest of the text pairs up normally.
03 One key for 27 February
Weierud reads the discriminator as the last three letters of the group, in any order, and cites the Bletchley note that four discriminators were issued with each daily key. Read that way, with the O/Q confusion allowed for in one group, the seven parts of 27 February use exactly four:
| discriminator | parts |
|---|---|
| WOJ | D33 (KFWOJ); D67 part 1 (OWQWJ, read OWOWJ) |
| OSX | D34 part 1 (DNOSX); D67 part 2 (WNSXO) |
| ANR | D34 part 2 (DRANR); E72 part 2 (LENRA) |
| WYY | E72 part 1 (SBWYY) |
Two parts of one message carry different discriminators, and the Mogilev and Proskurov traffic shares them. The four are therefore four names for one key, and all seven parts, 970 letters, can be attacked together. C43’s XOS is also OSX, which suggests either that it is misdated by a year or that a key ran for more than one day; Weierud already noticed the shared indicator. C43 was kept out of the pooled text.
04 The system
Two manuals survive and both were read here: the OKH draft Schlüsselanleitung zum Doppelkastenschlüssel of 2 December 1940 and the OKW issue of December 1941. They agree. There are two 5×5 boxes. The plaintext is written in double lines of 17 letters, and the pairs are the letters that stand one under the other; a remainder too short for a double line is split in half. Each pair is enciphered twice with the same boxes. When the two letters of a pair fall in the same row, each cipher letter is taken from the right. The spelling rules are CH → Q, ß → SZ, and the hyphen written STRIQ. The reference implementation, dk.py, enciphers both manuals’ worked examples letter for letter (the 1941 manual prints tylhu on p. 5 for the tvlhu it has correctly on p. 6).
The Army’s “NI” field cipher, as NSA’s Cryptologic Quarterly describes it, is the same scheme with 21-letter lines and the left neighbour for same-row pairs; its worked examples (EU → VR → QZ, en → FL → NC, ea → HK → TT) also reproduce exactly. Which variant the police used is not documented, so every attack here was run under both. That February 1942 is the double-box period at all rests on Hanyok: the police changed from double transposition to double Playfair between September and early November 1941, and Bletchley found the new system easier to read.
The cross-row identity
The double pass is what matters. After the first pass the first letter of a pair has been taken from box B, but the second pass looks it up in box A. So each letter is used by its position in both boxes. Write each letter as four coordinates: its row and column in A and its row and column in B. Deciphering a cipher pair (c1, c2) in the cross-row case gives the intermediate pair
v1 = A[ B-row of c1 ][ A-col of c2 ] v2 = B[ A-row of c2 ][ B-col of c1 ]
and the plaintext pair
p1 = A[ B-row of v1 ][ A-col of v2 ] p2 = B[ A-row of v2 ][ B-col of v1 ]
Every step mixes an A-coordinate with a B-coordinate. Between the two passes sits the map from each letter’s cell in box A to its cell in box B, a random permutation of the 25 cells. Section 06 shows why that permutation, rather than the size of the key, is what stops the attacks.
05 What was tried
A failed search proves nothing without a control, so each attack on the double encipherment was first run on synthetic text: 970 letters of German text enciphered twice with random boxes by mksyn.py and dk.py, in parts of the same lengths as the real ones. Scores are quadgram log-probabilities per letter from 25-letter German tables (build_lm.py); German text scores about −3.9 and random letters about −6.
| hypothesis or method | result | verdict |
|---|---|---|
| Single encipherment, all seven parts pooled, line length 1–30, pairs in written and reversed order | best −5.91 per letter against about −3.9 for German | excluded |
| Single encipherment per part and per discriminator group, line lengths 1, 17 and 21, both pair orders | −5.0 to −5.6, the level at which short texts overfit noise | excluded |
Double encipherment, simulated annealing on quadgrams (dksolve2.cs) | on the synthetic 970-letter text it never leaves noise (−6.0); started four swaps from the true key, it does not climb back | fails on its control |
Staged unigram → bigram → quadgram scoring; steepest-ascent iterated local search; parallel tempering; relabelling moves; a row/column product score; Sinkhorn relaxation on GPU (relax.py); hard EM with the plaintext as a variable (emsolve.cs) | all fail on the synthetic text. Random starts stall at unigram score −1.39 to −1.42, where the true key scores −1.221 | fails on its control |
| The same, with the true A-cell → B-cell permutation given and only the letter labelling searched | still stalls at −1.42 | fails on its control |
| SAT solving (z3) with 20 to 37 known pairs | no answer within 15 minutes for any case | inconclusive |
Known plaintext, meet-in-the-middle score (kpsolve.cs): encipher the plaintext pair once, decipher the cipher pair once, and score row-by-row and column-by-column agreement of the two intermediates | from random starts, recovers the boxes from 111 synthetic pairs in about a minute (111 of 111 consistent); 80 pairs nearly (68 of 80); fails at 60 or fewer, or with 10% of the pairs wrong | works on its control |
Key-free alignment of Bletchley’s A115 text to its ciphertext (align115c.py, align115h.py): repeated cipher pairs must match repeated plaintext pairs; exact backtracking over spellings, X separators, numbers written out, address and signature placement, Borki / Mohilew / Bobruiisk variants, 222 or 224 letters, line lengths 1–24 | no consistent alignment. The same search recovers a synthetic spelling in 35 seconds | no alignment |
The single-encipherment results exclude that hypothesis: the pooled text is 970 letters, far more than a single-pass double box needs, and the best key found is nowhere near German. The double-encipherment results exclude nothing about the plaintext, because the same methods fail on text where the answer is known. They measure the attacks, not the messages.
The alignment result does say something. A115’s repeated cipher pairs are real (section 02), and the aligner, given a synthetic text spelled in one of the ways it tries, finds it. On A115 no combination of spellings, separators, numbers, layout and length makes every repeated cipher pair fall on a repeated plaintext pair. Either the words enciphered differ from Bletchley’s rendering, through abbreviation, order or extra matter such as a longer address, or the police layout differs from both manuals.
06 Why the second pass defeats local search
This matches the public record. Hill-climbing solvers by Lasry, Norbert and others break single-pass double-box texts of 100 to 150 letters, and the Army’s Truppenschlüssel, a simplified one-pass relative, has been broken ciphertext-only from surviving messages. No published attack breaks a double pass through the same boxes. The synthetic runs here show where the difficulty lies.
- The true key is the optimum, but a narrow one. Under the unigram score the true key is the best key (−1.221). Its basin is only about eight to ten swaps wide. Under the quadgram score it is narrower still: annealing started four swaps from the true key does not climb back.
- Outside the basin there is nothing to climb. Random starts stall at −1.39 to −1.42. The local optima they reach share no row or column structure with the true boxes: measured as the precision of their row and column groupings against the key, they score about one in six, which is chance.
- Size is not the problem. Giving the solver the true permutation of cells between box A and box B, which leaves only the labelling of the letters to find, does not help: it still stalls at −1.42. The difficulty is the conjugation through a random cell permutation (section 04). The likely mechanism: a key that is partly right gives intermediate letters that are wrong, and the second pass sends those to unrelated cells, so partial correctness earns no score.
Known plaintext changes this, because it removes the second pass from the scoring. kpsolve.cs enciphers each known plaintext pair once forward, deciphers its cipher pair once backward, and asks the two intermediates to agree row by row and column by column. Each half is a single pass, which gives the local search a gradient; on the synthetic controls about 80 pairs are enough.
07 What would finish it
not solved Closed on the ciphertext alone. Two routes would reopen it.
| what | why it would work |
|---|---|
| About 80 plaintext–ciphertext pairs for one key | kpsolve.exe then recovers the boxes, and the key reads all seven parts of 27 February. The source would be Bletchley’s own decrypts of that day’s traffic, which should be in TNA HW 16/17 (verbatim German police decrypts; HW 16/18 is May–June 1942), not online; or a long stereotyped preamble or address common to the messages. For A115 the pairs need the exact German wording that was enciphered. |
| A115 aligned | It would also settle the police layout, lines of 17 or 21 and the left or right neighbour, and show whether police boxes were random or built from a keyword. Keyword-built boxes would open a keyword search on 27 February. |
| A new ciphertext-only idea for a second pass through the same boxes | Nothing tried here provides one, and nothing published does. |
08 Sources
- Frode Weierud, German Police Doppelkastenschlüssel Messages, Crypto Cellar Research, 5 August 2020, from NARA, RG 457, HCC, Box 1386 (NR. 4417) and Box 202 (NR. 976), with the teleprinter copies, the Red Forms of stations 51 and 43, and the Bletchley note on the preamble.
- Klaus Schmeh, “Can you solve these double-box messages from WW2?”, Cipherbrain, 30 August 2020.
- OKH, Schlüsselanleitung zum Doppelkastenschlüssel (draft, 2 December 1940), and OKW, Vorläufige Schlüsselanleitung zum Doppelkastenschlüssel (December 1941), both as scanned on cryptocellar.org.
- Robert J. Hanyok, Eavesdropping on Hell: Historical Guide to Western Communications Intelligence and the Holocaust, 1939–1945 (NSA Center for Cryptologic History, 2005): the A115 intercept, Red Form and decrypt, and the police change to double Playfair in autumn 1941.
- “WWII German Army Field Cipher”, NSA Cryptologic Quarterly, via the Wayback Machine: the Army NI variant and its worked examples.
- Modern Cryptanalysis of the Truppenschlüssel (22 October 2021): the Army’s simplified one-pass relative, and the two manuals’ provenance.
Everything is in orpo1942/ in the project repository: msgs.txt (the ciphertexts, discriminators removed), dk.py (reference implementation, verified on both manuals), build_lm.py (25-letter German n-gram tables), mksyn.py (synthetic test sets), dksolve2.cs (annealing, iterated local search and tempering with staged scoring), kpsolve.cs (the known-plaintext solver), emsolve.cs and relax.py (failed attacks, kept for the record), align115c.py and align115h.py (the A115 aligners), the source PDFs, and NOTES.md, which holds the full record of the attempt.