Solving PK1 - PK7 at Paradigm's CTF

Me trying and failing at solving PK8

Aug 15th, 2026
cryptographyctfc++pythoninvestigation
Scattered white letters on black, a still from Paradigm's Kryptos explainer video
From Paradigm's short explainer video on Project Kryptos.

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.

The author at DEF CON next to a neon sign reading Crypto means Cryptography
At DEF CON this year

Three techniques for the most part

The first seven puzzles mostly remix three classical ciphers:

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 official Paradigm Kryptos CTF page for PK7, showing its ciphertext and leaderboard
PK7 on the live CTF page. The interface gives you ciphertext.

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 solved PK7 page showing the recovered plaintext about the Whitesmith
The same page after the solve, with the full recovered plaintext.

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

Paradigm Kryptos CTF's PK8 page showing its ciphertext, 203 attempts, and no solvers
The PK8 page: 203 attempts, no solvers yet.

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.

The official Paradigm Kryptos CTF puzzle list and PK1 leaderboard
The challenge runs from approachable classical ciphers to PK8, PK9, and PK10, which still have no public solve.

What I tried

I started with the obvious reading of the clues and widened only after testing each smaller model.

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.

Get notified for new blog posts