Service Info

Status--
Endpointhttp://docker:8085
PQC AlgorithmML-KEM-1024 + AES-256-GCM + ML-DSA-65
FIPS StandardFIPS 203 (ML-KEM) + FIPS 204 (ML-DSA)

Where PQC is Used

🔐
PQC Handshake (Key Establishment)

ML-KEM-1024 encapsulation establishes a shared secret between peer and gateway. Both derive the same AES-256-GCM session key without ever transmitting it.

ML-KEM-1024 (FIPS 203, Level 5)
🛡️
Gateway Identity Verification

Gateway signs the handshake transcript with ML-DSA-65. Peers verify to ensure they negotiated with the real gateway, not a MITM.

ML-DSA-65 (FIPS 204, Level 3)
🔒
Tunnel Encryption

Every packet through the tunnel is AES-256-GCM encrypted using the KEM-derived session key. Decrypted on the other side. Same model as WireGuard ChaCha20/AES-GCM.

AES-256-GCM (with ML-KEM-derived key)
🔄
Session Rekey

Periodic re-encapsulation generates a fresh session key. The old key is zeroised immediately. Bounds the window of a compromised session key — PQ forward secrecy.

ML-KEM-1024 (FIPS 203, Level 5)

Description

This service simulates a WireGuard-style post-quantum VPN gateway. It demonstrates the exact handshake and tunnel-encryption patterns your VPN infrastructure will use after migrating to the Qudo JNI provider.

Handshake: Peer encapsulates to gateway's ML-KEM-1024 public key (provider.kemEncapsulate(gatewayPubKey, "ML-KEM-1024")). Both sides derive the same 256-bit AES session key. Gateway proves its identity with an ML-DSA-65 signature on the handshake transcript.
Tunnel: All traffic is AES-256-GCM encrypted with the KEM-derived key. Each packet gets a fresh random IV.
Rekey: On-demand new KEM encapsulation. Old session key is zeroised — forward secrecy per rekey interval.

GET /api/vpn/gateway-info Get gateway identity + ML-KEM-1024 public key
🔒 Returns the gateway's ML-KEM-1024 public key and ML-DSA-65 identity key. Peers use the KEM pubkey for handshake encapsulation.
POST /api/vpn/register-peer Register a new VPN peer
🔒 Qudo provider: provider.generateKeyPair("ML-KEM-1024"). Peer receives a tunnel ID, assigned IP, and its own KEM keypair.
POST /api/vpn/handshake Perform PQC handshake — establish session key
🔒 Full handshake: kemEncapsulate → decapsulate → ML-DSA-65 sign transcript. Response includes the signature, transcript, and gateway identity pubkey so the peer can verify independently. sharedSecretMatch confirms both sides derived the same key.
POST /api/vpn/verify-handshake Verify gateway identity (peer-side check)
🔒 Peer-side verification: provider.verify(transcript, sig, gatewayPub, "ML-DSA-65"). Copy signature + transcript from the handshake response. Gateway pubkey is used automatically (in production, you'd pin it in your client config). Detects MITM key-swap attacks.
POST /api/vpn/send-packet Send encrypted data through tunnel
🔒 AES-256-GCM encrypt with the KEM-derived session key. Add "size": 1400 to send N random bytes instead (max 65536) — use this to test realistic packet sizes and measure throughput. Response shows plaintext/ciphertext bytes and GCM overhead.
POST /api/vpn/rekey Rotate session key (fresh KEM encapsulation)
🔒 New ML-KEM-1024 encapsulation. Old session key zeroised. Equivalent to WireGuard's 2-minute rekey interval but triggered on demand here.
POST /api/vpn/disconnect Tear down tunnel and zerosise session key
🔒 Session key is immediately zeroised in memory. Tunnel ID removed. No data from this session can be decrypted after disconnect.
GET /api/vpn/tunnels List all active tunnels + traffic stats
🔒 Shows all peers, session status, packet/byte counters, and rekey count per tunnel.
GET /api/vpn/health Service health
🔒 Returns service status, active peer and session counts.

VPN & Private Networks Migration Reference

Migrate your VPN/IPSec/WireGuard infrastructure to quantum-safe key exchange and tunnel encryption using the Qudo JNI provider.

1. Architecture 2. Try It (Interactive) 3. Migrate Your System 4. Performance 5. Pitfalls 6. FAQ

1. VPN Architecture

A PQ-VPN gateway mirrors WireGuard's handshake model, replacing classical X25519 DH with ML-KEM-1024 encapsulation and adding ML-DSA-65 for gateway identity proof.

Peer
Gateway
Sends registration request
1. Register
Generates ML-KEM-1024 keypair for peer, assigns tunnel ID + IP
provider.generateKeyPair("ML-KEM-1024")
2. KEM Encapsulate
provider.kemEncapsulate(gatewayPub, "ML-KEM-1024")
Sends KEM ciphertext (1,568 bytes)
Decapsulate
provider.kemDecapsulate(ct, gatewayPriv, "ML-KEM-1024")
Both sides now share 256-bit secret
Verify
provider.verify(transcript, sig, gatewayPub, "ML-DSA-65")
Confirms gateway is genuine
3. Sign handshake
provider.sign(transcript, identityKey, "ML-DSA-65")
Sig: 3,309 bytes
4. Encrypted Tunnel
AES-256-GCM with KEM-derived key
Fresh random IV per packet, 128-bit auth tag
5. Rekey
Fresh kemEncapsulate()
Old key zeroised. New session key active. Forward secrecy per rekey.
6. Disconnect
Session key zeroised. Tunnel removed.
Why ML-KEM-1024? VPN tunnels are long-lived and carry the most sensitive traffic. Cat 5 provides the highest PQC security, and the <0.15ms encapsulation cost is negligible compared to network RTT. The 1,568-byte ciphertext fits in a single standard MTU frame.

2. Try It (Interactive)

Walk through a full PQ-VPN session using the API Test tab above. Follow this order:

  1. GET /api/vpn/gateway-info — see the gateway's ML-KEM-1024 public key and ML-DSA-65 identity
  2. POST /api/vpn/register-peer — register as a VPN peer, get a tunnel ID
  3. POST /api/vpn/handshake — perform the PQC handshake (copy your tunnelId from step 2). Response includes the gateway's ML-DSA-65 signature, transcript, and identity pubkey.
  4. POST /api/vpn/verify-handshake — copy signature, transcript, and gatewayIdentityPublicKey from step 3 to independently verify the gateway is genuine (detects MITM).
  5. POST /api/vpn/send-packet — send encrypted data through the tunnel
  6. POST /api/vpn/rekey — rotate the session key (old one zeroised)
  7. POST /api/vpn/send-packet again — works with the new key
  8. POST /api/vpn/disconnect — tear down

Check GET /api/vpn/tunnels at any point to see packet/byte counters and rekey history.

3. Migrate Your System

Three components change. Everything else (routing, firewall rules, DNS, client apps) stays the same.

A. Handshake — Replace DH/ECDH with ML-KEM

BEFORE (classical DH)
// WireGuard-style X25519 byte[] sharedSecret = x25519(myPriv, peerPub);
AFTER (PQC via Qudo JNI)
// Peer side (initiator): QudoCrypto provider = QudoCrypto.create(); QudoKemResult kem = provider.kemEncapsulate( gatewayPubKey, "ML-KEM-1024"); byte[] sharedSecret = kem.getSharedSecret(); byte[] kemCiphertext = kem.getCiphertext(); // Send kemCiphertext to gateway // Gateway side (responder): byte[] sharedSecret = provider.kemDecapsulate( kemCiphertext, gatewayPrivKey, "ML-KEM-1024"); // Both now have identical 32-byte shared secret

B. Gateway Identity — Sign the handshake transcript

// GATEWAY SIDE: sign transcript (tunnelId + KEM ciphertext + peer pubkey hash) // Binds this handshake to this specific peer — prevents cross-peer replay. String transcript = tunnelId + ":" + base64(kemCiphertext) + ":" + peerPubHash; byte[] sig = provider.sign(transcript.getBytes(), gatewayIdentityPrivKey, "ML-DSA-65"); // Return sig + transcript + gatewayIdentityPubKey to peer. // PEER SIDE: independently verify gateway before sending any traffic. // This is the MITM check — if an attacker swapped KEM pubkeys, // they can't produce a valid ML-DSA-65 signature. boolean legit = provider.verify( transcript.getBytes(), sig, gatewayIdentityPubKey, "ML-DSA-65"); if (!legit) throw new SecurityException("Gateway identity check failed — possible MITM");

C. Tunnel Encryption — Stays AES-256-GCM

// Derive AES key from KEM shared secret (same on both sides) byte[] aesKey = Arrays.copyOf(sharedSecret, 32); // Encrypt a packet (standard Java — no Qudo call needed) Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), new GCMParameterSpec(128, randomIv)); byte[] ciphertext = c.doFinal(plaintext);
What changes: Key exchange (DH → ML-KEM-1024) and identity proof (RSA/ECDSA → ML-DSA-65).
What stays the same: Symmetric tunnel cipher (AES-256-GCM), routing, firewall rules, DNS, client applications, MTU handling.

D. Session Rekey

// Periodic rekey — same encapsulate/decapsulate as handshake QudoKemResult rekem = provider.kemEncapsulate(gatewayPubKey, "ML-KEM-1024"); byte[] newSecret = rekem.getSharedSecret(); byte[] newAesKey = Arrays.copyOf(newSecret, 32); // Zeroize old key immediately Arrays.fill(oldAesKey, (byte) 0); // WireGuard rekeys every 2 minutes; adjust interval to your policy

4. Performance

Measured via Qudo JNI on Apple M-series, single-thread. Run Benchmark for your hardware.

OperationAlgorithmLatencyWire Size
KEM keygenML-KEM-1024~0.15msPub: 1,568B
EncapsulateML-KEM-1024~0.12msCT: 1,568B
DecapsulateML-KEM-1024~0.12ms
Identity signML-DSA-65~0.5msSig: 3,309B
Identity verifyML-DSA-65~0.2ms
Tunnel encryptAES-256-GCM<0.01ms+28B overhead (IV+tag)

Total handshake overhead vs classical WireGuard: ~0.8ms extra (KEM encap/decap + sig). On a 50ms RTT link, this is invisible. The KEM ciphertext (1,568B) adds one extra packet at most.

5. Common Pitfalls

PitfallImpactFix
Reusing KEM ciphertextShared secret replay — attacker can decrypt trafficFresh kemEncapsulate() on every handshake/rekey. Never cache ciphertext.
Not zeroising old session keyCompromised memory leaks past trafficArrays.fill(oldKey, (byte) 0) immediately after rekey/disconnect
Skipping identity verificationMITM can substitute their own KEM pubkeyAlways verify ML-DSA-65 signature on the handshake transcript. Pin the gateway pubkey in config.
MTU too low for KEM ciphertextFragmentation during handshakeML-KEM-1024 ciphertext is 1,568B. Fits in one standard 1500-MTU frame. But on tunnels with overhead (GRE/IPsec), increase outer MTU or allow fragmentation during handshake only.
Rekey interval too longSession key exposure window growsWireGuard rekeys every 2 min / 2^64 bytes. Apply the same policy with PQC — ML-KEM encapsulation is cheap enough.

6. FAQ

Q: Can I use ML-KEM-768 instead of 1024?
Yes. ML-KEM-768 (Cat 3, 1,088B ciphertext) is fine for most scenarios. Use 1024 if your threat model includes nation-state adversaries or the tunnel carries classified data. Both are provider.kemEncapsulate(pubKey, "ML-KEM-768") — same API, different string.
Q: Does PQC change the tunnel cipher (AES)?
No. AES-256-GCM is already quantum-safe for symmetric encryption (Grover's algorithm halves the key strength to 128-bit equivalent, which is still secure). Only the key exchange needs migrating.
Q: What about hybrid (X25519 + ML-KEM) for VPNs?
The Choose Your Algorithm page covers this. For TLS, hybrid is recommended during transition. For VPNs where you control both endpoints, going pure ML-KEM is simpler and avoids the classical dependency. If you need backwards compatibility with non-PQC peers, run hybrid.
Q: Does WireGuard officially support ML-KEM?
Not yet in mainline. The Rosenpass project adds a PQC key exchange layer on top of WireGuard. The handshake pattern in this simulator matches what Rosenpass implements — so migrating from this playground to a real Rosenpass+WireGuard deployment is 1:1.

7. Playground vs. Production

This simulator demonstrates the cryptographic patterns. A production PQ-VPN will additionally need:

FeaturePlaygroundProduction
Two-sided handshake Both sides run server-side in one API call Peer runs kemEncapsulate() locally, sends only kemCiphertext (1,568B) to gateway. Same Qudo JNI calls, different process boundary.
Hybrid mode Pure ML-KEM-1024 only Combine X25519 + ML-KEM-768 shared secrets (XOR or HKDF) for backwards compatibility during transition. Same kemEncapsulate() call — add a classical DH alongside it.
Automatic rekey On-demand via /rekey Timer-based (WireGuard: every 2 min) or byte-count-based (after 264 bytes). Wrap the same kemEncapsulate() in a scheduled task.
MTU / fragmentation Not simulated ML-KEM-1024 ciphertext (1,568B) fits one 1500-MTU frame. On tunnels with encapsulation overhead (GRE, IPsec), set inner MTU to ~1400 or allow handshake fragmentation. MSS clamping recommended.
Peer-to-peer mesh Client ↔ gateway only For mesh, each peer pair runs an independent handshake. The same kemEncapsulate() / kemDecapsulate() pattern applies — just N×(N-1)/2 of them.
Throughput testing /send-packet with optional "size": N (up to 64KB) For sustained throughput benchmarks, use the Benchmark page — AES-256-GCM speed doesn't change with PQC (only the handshake does).
The key takeaway: every production feature listed above uses the exact same Qudo JNI calls you see in this playground (kemEncapsulate, kemDecapsulate, sign, verify). The protocol scaffolding changes; the crypto calls don't.

Crypto Inventory Scanner

Analyze any endpoint's cryptographic configuration. Enter a host:port to scan its TLS setup and identify what's quantum-safe vs what needs migration.

Scan Endpoint