A Different Road to Quantum Safety: The PPK – IPsec Series, Part 5

Retail Online Training


So far in this pillar we made the key exchange itself quantum-safe by bolting a post-quantum algorithm (ML-KEM) onto it. That’s the headline approach, and in Part 4 we watched it run. But it’s not the only way to beat “harvest now, decrypt later.” There’s an alternative road that gets you to the same destination without any new crypto algorithm at all.

It’s called a Postquantum Preshared Key (PPK), defined in RFC 8784. Let’s understand it, then prove it works.

Note: this Part uses the same key-exchange/ lab as Part 4.


The idea: mix in a secret that never travels

RFC 8784 takes a completely different route to the harvest-now-decrypt-later problem. Instead of a new algorithm, it mixes a static, out-of-band preshared key into the IKE key schedule. The key property: the PPK never travels on the wire. It’s distributed ahead of time, so both peers already know it.

Here’s why that defeats a future quantum computer. Suppose an attacker records your handshake today and, years from now, uses a quantum computer to recover the X25519 shared secret from it. They still can’t derive your traffic keys, because those keys also depend on the PPK, which was never transmitted and which their recording therefore doesn’t contain. No PPK, no keys. The recorded session stays locked.

Two roads, one destination. ML-KEM (RFC 9370) makes the math quantum-hard and is negotiated like any other algorithm. PPK (RFC 8784) makes no new cryptographic assumption about the exchange; it just requires that a high-entropy secret stays secret and is shared out of band. That means PPK works even on legacy gear that cannot negotiate ML-KEM, which makes it a practical first step: get post-quantum confidentiality today, migrate to ML-KEM once both ends support it.

The catch is operational, and it’s a big one: every peer pair needs the same secret pre-shared and rotated, which is exactly the key-distribution headache public-key crypto was invented to avoid. (At scale, the secrets are either managed manually or generated by Quantum Key Distribution appliances needing a full mesh of point-to-point fibers, none of which scales gracefully.) So PPK is a transitional step, not a destination. Also note: PPK protects only the derived keys; authentication here is still a classical PSK.


Set up the lab

This uses the same two-container key-exchange lab as Part 4. If you don’t already have it running, clone the repo, build, and start both peers (only Docker required):

git clone https://github.com/juliogomez/pqc.git
cd pqc/key-exchange


docker compose build
docker compose up -d
docker compose ps        

Let’s prove it: drop to classical KE and let the PPK carry it

To make the “alternative to ML-KEM” point crystal clear, this exercise drops the key exchange back to classical-only X25519 on purpose (standing in for a legacy device that does not support ML-KEM) and lets the PPK provide the quantum resistance entirely on its own.

Shell into the initiator (docker exec -it ike-initiator bash) and edit swanctl.conf. First, switch the proposal to classical-only (same toggle as Part 4):

# proposals = aes256gcm16-prfsha256-x25519-ke1_mlkem768
proposals = aes256gcm16-prfsha256-x25519

Then uncomment the two PPK lines in the connection:

        ppk_id = pqc-lab-ppk
        ppk_required = yes

And uncomment the secret block at the bottom:

    ppk-lab {
        id = pqc-lab-ppk
        secret = 0x5c9a3f1e8b7d4602af19e3c8d05b6f27a4e91d83c2b7f0a6e5d4c3b2a1908f7e
    }

Make the same edits in the responder, then reload both with swanctl --load-all. The output now lists the PPK alongside the auth PSK:

loaded ike secret 'ike-psk'
loaded ppk secret 'ppk-lab'

Reminder: these are lab secrets. That hex value is a throwaway for learning. In the real world a PPK is a high-entropy secret you’d generate securely, distribute out of band, store carefully, and rotate. Never reuse lab keys anywhere that matters.


Initiate and confirm the PPK is in play

From the initiator:

swanctl --initiate --child pqc-child

Four lines in the log tell the whole story:

[ENC] generating IKE_SA_INIT request 0 [ SA KE No ... N(USE_PPK) ]
[CFG] selected proposal: IKE:AES_GCM_16_256/PRF_HMAC_SHA2_256/CURVE_25519
[ENC] generating IKE_AUTH request 1 [ IDi AUTH SA TSi TSr ... N(PPK_ID) ]
[CFG] using PPK for PPK_ID 'pqc-lab-ppk'

Notice what’s missing compared to Part 4: the selected proposal is plain CURVE_25519: no KE1_ML_KEM_768, no IKE_INTERMEDIATE, no fragments. This is an old-fashioned classical handshake. And yet it’s now quantum-resistant, entirely thanks to the PPK:

  • N(USE_PPK) rides in the cleartext IKE_SA_INIT, each peer advertising support.
  • N(PPK_ID) rides inside the encrypted IKE_AUTH, signalling which PPK is in use.
  • using PPK for PPK_ID 'pqc-lab-ppk' is strongSwan confirming the secret was found and mixed into the key schedule.

If you capture the run (same tcpdump recipe as Part 4), three contrasts with the ML-KEM runs stand out:

  • No #43, no fragments. Just the plain two-round-trip classical handshake. The quantum resistance is riding entirely on the PPK, invisible in the packets.
  • USE_PPK is public; PPK_ID is private. Discovery (“do we both support it?”) is in the cleartext IKE_SA_INITwhich PPK is selected travels encrypted inside IKE_AUTH.
  • The PPK itself appears nowhere. That’s the whole point: mixed into the keys on both ends, never sent.

Prove it’s actually required

Because we set ppk_required = yes, a peer that doesn’t hold the matching PPK can’t complete the handshake. Let’s prove the secret is genuinely folded into the keys. Terminate the tunnel, then, in the initiator only, change its ppk-lab secret to a different value (simulating a peer that doesn’t know the real one):

    ppk-lab {
        id = pqc-lab-ppk
        secret = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
    }

Reload just the initiator and re-initiate. This time it fails: the mismatched PPK changes the SK_pi/SK_pr keys, so the responder can’t verify the initiator’s AUTH payload and rejects it:

[ENC] parsed IKE_AUTH response 1 [ N(AUTH_FAILED) ]
[IKE] received AUTHENTICATION_FAILED notify error
initiate failed: establishing CHILD_SA 'pqc-child' failed

There’s your proof: the PPK isn’t decorative; it’s baked into the keys. Get it wrong and the tunnel simply won’t come up.

Tidy up your clone. Because config/ is bind-mounted, your vi edits were written to your repo files. Since they’re git-tracked, one command from the host (in key-exchange/) snaps both peers back to the shipped state: git restore config/. That re-comments the PPK lines, restores the secret, and switches the proposal back to hybrid: a clean slate for the next run.


When would you actually use this?

Mainly as a stepping stone. Equipment that can’t negotiate RFC 9370 / ML-KEM yet, but needs harvest-now-decrypt-later protection today, can deploy a PPK now and migrate to ML-KEM once both ends support it. The price is the classic preshared-key burden (secure distribution, storage, and rotation of a ≥256-bit secret across every peer pair), which is exactly why it’s a transition, not a home.

So the two roads complement each other: PPK for “I need protection now on legacy gear,” ML-KEM for “I can negotiate it and want it to scale.” Plenty of real deployments will use both during the migration years ahead.


That’s a wrap on key exchange

Over the last four posts we’ve taken the urgent pillar end to end: the threat, the contenders, the hybrid showdown, a real captured handshake, and now a second independent road to the same safety. You’ve seen, really seen, that quantum-safe key exchange is here, practical, and affordable today.

Now we flip to the other pillar, and it behaves completely differently. No “harvest now, decrypt later” here, but a sneakier deadline that catches almost everyone off guard. In Part 6 we start the authentication story and meet the post-quantum signatures. See you there, and bring your curiosity, because this half has a twist.

Retail Online Training