Paradigm released a Kryptos CTF to celebrate them aquiring the solution to Kryptos K4- ten classical-cryptography puzzles that tell one continuous story and get harder as they go on. The first seven have been solved, and were solved fairly quickly but PK8, 9 and 10 have not.
I rebuilt PK1 through PK7, then spent a few days (tokens) on PK8. This is what the earlier puzzles taught me and what I tested.
Three techniques for the most part
The first seven puzzles mostly remix three classical ciphers:
- Quagmire III (Q) substitutes each letter using a scrambled alphabet and a repeating key. Think of it as a Vigenère-style letter shift, except the ruler is labeled
KRYPTOSABCDEFGHIJLMNQUVWXZinstead of ordinary A–Z. - Columnar transposition (T) does not change the letters. It writes them into rows, then reads the columns back in an order chosen by a keyword.
- Hill cipher (H) turns small groups of letters into numbers and mixes them with matrix multiplication modulo 26.
A candidate that merely looks like English is not always a solve. For every puzzle I wrote a forward-encryption proof: starting from the proposed plaintext and keys, and had to reproduce every ciphertext character exactly.
Here is the complete PK1 proof, minus the long plaintext and ciphertext constants:
ALPHABET = "KRYPTOSABCDEFGHIJLMNQUVWXZ"
KEY = "PROVENANCE"
encrypted = "".join(
ALPHABET[
(ALPHABET.index(ch) + ALPHABET.index(KEY[i % len(KEY)])) % 26
]
for i, ch in enumerate(PLAINTEXT)
)
assert encrypted == CIPHERTEXT
Notice the last line where we compare the encrypted text to the ciphertext to ensure we actually have a forwards and backwards solve.
PK1 through PK7
The sequence is easier to see as a table. Encryption happens from left to right; decryption (obviously) reverses it.
| Puzzle | Construction | Key material |
|---|---|---|
| PK1 | Q, period 10 | PROVENANCE |
| PK2 | T, width 7 | MARGINS |
| PK3 | Q(10), then Q(8) | PENTIMENTO, ORDINATE |
| PK4 | T(8), Q(5), Q(9) | UNDERLAY, OCHRE, VERDIGRIS |
| PK5 | T(8), then Q with a 224-letter running key | TWOYEARS, then the full PK4 plaintext |
| PK6 | T(9), T(9), Q(6) | HANDIWORK, SMITHWORK, PORTAL |
| PK7 | Q(6), then a 3Ă—3 Hill cipher | ANNEAL, ALCHEMIST |
PK1 is the introduction. Removing the repeating PROVENANCE shifts reveals the first part of the storyline.
PK2 swaps substitution for position: write the plaintext in seven columns, read the columns in MARGINS order. Reversed, it becomes plaintext. The transposition fits in two lines:
order = sorted(range(len(KEY)), key=lambda i: (KEY[i], i))
encrypted = "".join(PLAINTEXT[i::len(KEY)] for i in order)
assert order == [1, 3, 4, 0, 5, 2, 6]
PK3 stacks two Quagmire layers. Both are additions in the same keyed alphabet, so their order does not matter. Removing ORDINATE and PENTIMENTO widens the search across six countries.
PK4 combines movement and substitution: undo VERDIGRIS, undo OCHRE, then put the eight UNDERLAY columns back where they belong.
PK5 is where the series becomes self-referential. The Quagmire key is not a short word at all: it is the entire 224-letter PK4 plaintext, repeated as needed. Remove that, then undo the TWOYEARS transposition. The previous answer has become part of the next lock.
PK6 uses two different nine-column transpositions (HANDIWORK and SMITHWORK) under an outer PORTAL Quagmire layer. Reverse all three for the plaintext.
PK7 removes an ANNEAL Quagmire layer only after inverting a 3Ă—3 Hill matrix built from ALCHEMIST:
7 17 9
14 11 18
15 6 4
The result says the narrator repeats the same step “four times with slight variations.” It also contains exactly four seven-letter words, in order:
DRAWING FALTERS PATIENT CALLING
That looks a lot like an instruction for PK8. Unfortunately, still wasn’t enough for me and 5.6-Sol to solve.
PK8
PK8 is 153 letters long. Its letter statistics look too flat for a simple rearrangement of English, which implies some form of substitution.
The strongest reproducible lead appears after “folding” the 26-position Kryptos alphabet into 13 pairs:
alphabet = "KRYPTOSABCDEFGHIJLMNQUVWXZ"
folded = [alphabet.index(ch) % 13 for ch in ciphertext]
streams = [folded[phase::7] for phase in range(7)]
Those seven streams are unusually language-like by index-of-coincidence tests, and PK9 independently shows almost the same folded period-seven signal. In a joint scan of periods 2 through 30, period 7 ranked first; a matched randomization test produced p = 0.0036. So something structured happens every seventh character, and PK8 and PK9 probably share a construction idea. That does not tell us the cipher or the key.
What I tried
I started with the obvious reading of the clues and widened only after testing each smaller model.
- Exhaustive Quagmire-plus-transposition searches: all
7! = 5,040column orders, independent shifts, both layer orders. No plaintext. - The four PK7 words as four transpositions, four substitutions, mixed Q/T rounds, a 4×7 Cardan grille, row and column permutations, and several “slight variation” readings. Individual exact censuses reached 15,543,360 candidates. No plaintext.
- PK7 itself as a running key, a long permutation key, a binary mask, an autokey source, and a Solitaire passphrase. No plaintext.
- Ordinary and custom Quagmire alphabets, Hill-plus-transposition combinations, turning grilles, rotor-like substitutions, generated keys, and split 2Ă—13 coordinate systems.
- Joint solvers that require PK8 and PK9 to obey the same underlying structure, since they share the period-seven signal. One dictionary-alphabet census checked 829,233 alphabets; larger heuristic searches ran up to 100 million proposals per seed.
My working repo now holds roughly 50 C++ solvers and 70 controller and analysis scripts. Somewhat frustratingly flexible cipher models are extremely good at manufacturing little English-looking fragments from noise.
To keep myself honest, every serious search gets a planted control. I encrypt known prose with a random key from the family under test and require the solver to recover it at the same budget I plan to spend on PK8. If it cannot, a miss on PK8 proves nothing. If it recovers the control and PK8 stays gibberish, I can reject that family, or at least that calibrated search envelope.
That discipline has caught several seductive dead ends. A quadgram score in the -800s can contain recognizable fragments and still be overfit nonsense; real 153-character narrative samples score around -620 to -672 in the same model.
What is left
The literal uses of DRAWING / FALTERS / PATIENT / CALLING are controlled nulls, along with simple Q7+T7, direct reuse of PK7, several grille families, shared pair-preserving alphabets, and a long list of exact finite catalogs.
The best remaining structured idea is a shared arbitrary rotor-like substitution plus one shared cyclic cut between PK8 and PK9. An unrestricted shared Quagmire-III alphabet with the same cut is another open family, but its search is less mature. Both survive necessary-condition tests; neither has produced positive plaintext evidence. The often-interesting cut value 197 is a prioritized slice but not a recovered key.
My guess from the hint tweets is that it’s got some horribly long key like all the previous plaintext answers combined or something, I’ll have to try that next when I have time.