Skip to main content
Mullvad VPN implements quantum-resistant tunnels using post-quantum key encapsulation mechanisms (KEMs) to protect against future quantum computer attacks. This document details the cryptographic protocols, PSK derivation, and ephemeral peer negotiation process.

Overview

Quantum-resistant tunnels add an additional layer of security to WireGuard by negotiating a pre-shared key (PSK) using quantum-safe KEMs. This PSK is mixed into WireGuard’s cryptographic handshake, ensuring that even if quantum computers break the elliptic curve cryptography in the future, past traffic remains secure.

Key Features

  • Post-Quantum KEMs: ML-KEM-1024 (NIST standardized) and HQC-256 for hybrid security
  • Ephemeral Peers: Short-lived peers with unique PSKs for each tunnel session
  • Multihop Support: Independent PSKs for entry and exit peers in multihop configurations
  • Automatic Negotiation: Transparent PSK exchange over the tunnel before full activation

Code References

  • Ephemeral peer negotiation: talpid-wireguard/src/ephemeral.rs
  • KEM client implementation: talpid-tunnel-config-client/src/lib.rs
  • ML-KEM implementation: talpid-tunnel-config-client/src/ml_kem.rs
  • HQC implementation: talpid-tunnel-config-client/src/hqc.rs
  • Protocol definition: talpid-tunnel-config-client/proto/ephemeralpeer.proto

Cryptographic Primitives

ML-KEM-1024 (FIPS 203)

Algorithm: Module-Lattice-Based Key-Encapsulation Mechanism Standardization: NIST FIPS 203 (formerly Kyber) Implementation (ml_kem.rs):
Security Level: NIST Level 5 (highest)
  • Provides 256-bit post-quantum security
  • Encapsulation key: 1568 bytes
  • Ciphertext: 1568 bytes
  • Shared secret: 32 bytes
Why ML-KEM-1024:
  • NIST standardized (published August 2024)
  • Fast performance with small keys
  • No practical benefit to using lower security levels

HQC-256

Algorithm: Hamming Quasi-Cyclic Implementation (hqc.rs):
Security Level: 256-bit post-quantum security
  • Encapsulation key: 7245 bytes (larger than ML-KEM)
  • Ciphertext: 13909 bytes
  • Shared secret: 64 bytes (hashed to 32)
Why HQC-256:
  • Code-based cryptography (different mathematical foundation than ML-KEM)
  • Provides defense-in-depth through algorithm diversity
  • NIST Round 4 candidate

PSK Derivation Protocol

KEM Combination via XOR

The final WireGuard PSK is derived by XORing the shared secrets from both KEMs (lib.rs:289-294):
Security Rationale: XOR mixing is secure because:
  • If either B or C is secret, A = B ⊕ C remains secret
  • Both B and C must be compromised to compute any bit in A
  • All involved KEMs must be broken before the PSK is revealed
  • WireGuard’s HKDF uniformly distributes entropy regardless of input distribution
From the protocol specification (ephemeralpeer.proto:103-119):
The PSK to be used in WireGuard’s preshared-key field is computed by XORing the resulting shared secrets of all the KEM algorithms. All currently supported and planned to be supported algorithms output 32 bytes, so this is trivial. Since the PSK provided to WireGuard is directly fed into a HKDF, it is not important that the entropy in the PSK is uniformly distributed. The actual keys used for encrypting the data channel will have uniformly distributed entropy anyway, thanks to the HKDF. Mixing with XOR (A = B ^ C) is fine since nothing about A is revealed even if one of B or C is known. Both B and C must be known to compute any bit in A.

Secret Zeroization

Shared secrets are zeroized immediately after use (lib.rs:194-213):
Security Note (ml_kem.rs:31-35):
Always inline in order to try to avoid potential copies of shared_secret to multiple places on the stack. This is almost pointless as with optimization all bets are off regarding where the shared secrets will end up in memory. In the future we can try to do better, by cleaning the stack. But this is not trivial.

Ephemeral Peer Negotiation

Protocol Overview

Quantum-resistant tunnels use ephemeral peers to avoid breaking existing tunnels during PSK negotiation:
  1. Normal Peer Connects: Client establishes tunnel with long-lived credentials
  2. Generate Ephemeral Keys: Client generates new WireGuard keypair and KEM keypairs
  3. Request Exchange: Client sends ephemeral public keys to server over tunnel
  4. Server Response: Server encapsulates secrets to client’s KEM public keys
  5. Derive PSK: Client decapsulates ciphertexts and derives PSK via XOR
  6. Switch to Ephemeral: Client reconfigures tunnel with ephemeral key + PSK
Mutual Exclusivity: Only one peer (normal or ephemeral) is active in WireGuard at a time. A handshake from the normal peer unloads the ephemeral peer and vice versa.

gRPC Service Definition

The ephemeral peer service (ephemeralpeer.proto) defines:

Client Implementation

The negotiation client (lib.rs:113-134):
Generate Secrets (lib.rs:268-287):

Service Configuration

Port: 1337 (CONFIG_SERVICE_PORT) Connection (lib.rs:296-317):
Special Socket Configuration: The socket uses a custom MSS (Maximum Segment Size) to avoid MTU issues during the handshake, since the tunnel MTU may not be optimal yet.

Timeout Configuration

Timeout increases with retry attempts (ephemeral.rs:21-23):
Calculation (ephemeral.rs:275-279):
Timeout Progression:
  • Attempt 0: 8 seconds
  • Attempt 1: 16 seconds
  • Attempt 2: 32 seconds
  • Attempt 3+: 48 seconds (capped)

Integration with WireGuard

Trigger Conditions

Ephemeral peer negotiation is triggered when (lib.rs:283, ephemeral.rs:100-108):
Both quantum-resistant and DAITA features require ephemeral peers.

Traffic Restrictions During Negotiation

During ephemeral peer negotiation, tunnel traffic is restricted (lib.rs:603-626):
After successful negotiation, all traffic is allowed (lib.rs:628-631).

Configuration Update

After deriving the PSK, the tunnel configuration is updated (ephemeral.rs:162-182):
Tunnel Reconfiguration involves:
  1. Stopping obfuscation (if active)
  2. Restarting obfuscation with new config
  3. Calling tunnel.set_config() to apply new WireGuard parameters

Multihop Quantum-Resistant Tunnels

Dual PSK Negotiation

In multihop mode, two separate PSKs are negotiated (ephemeral.rs:126-160):
  1. Exit Peer PSK: Negotiated first using exit relay’s config service
  2. Entry Peer PSK: Negotiated second using entry relay’s config service
Both peers use the same ephemeral WireGuard keypair but different PSKs.

Negotiation Sequence

Security Properties

Multihop + Quantum Resistance provides:
  1. Two Independent PSKs: Entry and exit peers each have unique quantum-resistant PSKs
  2. One Ephemeral Keypair: Same ephemeral WireGuard key used with both peers
  3. Nested Encryption: Traffic is encrypted twice:
    • Inner layer: Client ↔ Exit (with PSK₁)
    • Outer layer: Client ↔ Entry (with PSK₂)
Threat Model:
  • Attacker must break both quantum-resistant PSKs to decrypt traffic
  • Even if one relay is compromised, the other PSK protects the tunnel
  • Forward secrecy is maintained through ephemeral WireGuard keys

Windows-Specific Implementation

MTU Workaround

Windows requires a temporary MTU reduction during ephemeral peer negotiation (ephemeral.rs:26-60):
Rationale: Unix systems set MSS directly on TCP sockets, but this doesn’t work on Windows, requiring an interface-level MTU change.

Performance Characteristics

Key Generation Timing

Logged during negotiation (lib.rs:146-149):
Typical performance:
  • ML-KEM-1024 keypair generation: <1ms
  • HQC-256 keypair generation: ~5ms
  • Total: ~5-10ms

Network Overhead

Request Size:
  • WireGuard public keys: 2 × 32 bytes = 64 bytes
  • ML-KEM-1024 encapsulation key: 1568 bytes
  • HQC-256 encapsulation key: 7245 bytes
  • Protobuf overhead: ~100 bytes
  • Total: ~9KB request
Response Size:
  • ML-KEM-1024 ciphertext: 1568 bytes
  • HQC-256 ciphertext: 13909 bytes
  • DAITA configuration: ~500 bytes (if enabled)
  • Protobuf overhead: ~100 bytes
  • Total: ~16KB response
Round Trip: One request/response for single-hop, two for multihop

Error Handling

Ephemeral Peer Errors

Timeout Handling

Recovery: Timeouts are recoverable errors - the connection attempt will be retried with increased timeout.

Ciphertext Validation

Testing Example

The repository includes a test example (examples/psk-exchange.rs):