This repository contains the implementation developed as part of a master's thesis at the Technical University of Košice (TUKE), Faculty of Electrical Engineering and Informatics. The goal was to integrate post-quantum cryptographic algorithms into the WireGuard VPN protocol, with an emphasis on feasibility for IoT-class hardware.
WireGuard was originally developed by Jason A. Donenfeld. This work is an experimental modification of the original protocol and is not affiliated with or endorsed by the WireGuard project.
This is a research prototype. Do not use in production.
The original WireGuard uses Curve25519 for key exchange. This implementation replaces and extends that with:
- ML-KEM-512 (FIPS 203) for post-quantum key encapsulation, replacing the Curve25519-based ECDH exchange
- ML-DSA-44 (FIPS 204) for post-quantum digital signatures used in handshake authentication
- Both algorithms are implemented via the leancrypto library running directly in kernel space
- A new userspace tool wg-pqc that loads PQC keys into the kernel module over a Netlink socket using a JSON config file
The module supports three runtime modes that can be switched at load time without recompilation:
| Mode | Description |
|---|---|
curve25519 |
Original WireGuard, no changes |
static |
Replaces ECDH with a static pre-shared master key |
pqc |
Full post-quantum mode with ML-KEM-512 and ML-DSA-44 |
Testing was done in a local network with two Raspberry Pi 3B+ devices (ARM Cortex-A53, 1.4 GHz, 1 GB RAM) connected via a D-Link DIR-842V2 router over gigabit LAN. One device acted as the VPN server (RPi-A), the other as the client (RPi-B). The local setup was chosen to isolate the pure cryptographic overhead of PQC from internet variability.
[RPi-A server] [RPi-B client]
eth0: 10.0.0.57 eth0: 10.0.0.31
wg0: 10.10.0.1 wg0: 10.10.0.2
| |
+---[D-Link DIR-842V2]--+
(LAN 1Gbps)
| Metric | Original WireGuard | PQC version |
|---|---|---|
| Handshake latency | ~205 ms | ~430 ms |
| TCP throughput | no measurable difference | no measurable difference |
| Per-packet latency | no measurable difference | no measurable difference |
| Rekeying | no measurable difference | no measurable difference |
The only significant overhead is in handshake time (factor of ~2.1). Data transfer performance is not affected. Handshake times were measured from kernel logs (dmesg) over 30 runs per configuration.
.
├── *.c / *.h # Modified WireGuard kernel module source files
├── noise_pqc.c # PQC handshake logic
├── noise_init_pqc.c # PQC key initialization
├── peer_pqc.c # PQC peer state management
├── wg_mode.h # Runtime mode switching
├── uapi_wireguard_pqc.h # Extended Netlink API definitions
├── Makefile # Kernel module build
├── dkms.conf # DKMS configuration
└── tools/
├── wg-pqc.c # Main CLI entry point
├── wg-commands.c # Command implementations
├── wg-pqc-config.c/h # JSON config parser
├── wg-netlink.c/h # Netlink communication
└── Makefile # Tools build
- Linux kernel with kernel headers installed
- DKMS
- GCC, make
- curl or wget (for downloading cJSON during tools build)
- leancrypto library built for kernel space
The kernel module depends on leancrypto. Clone and build it first:
git clone https://github.com/smuellerDD/leancrypto.git
cd leancrypto/linux_kernel
make
sudo insmod leancrypto.koKeep note of the path where you cloned leancrypto, you will need it in the next step.
Open the kernel module Makefile and set LEANCRYPTO_DIR to match your local path:
LEANCRYPTO_DIR := /home/youruser/leancryptomakeOr install it with DKMS for automatic rebuilds on kernel updates:
sudo dkms add .
sudo dkms build wireguard/1.0
sudo dkms install wireguard/1.0cd tools/
make
sudo make install # installs wg-pqc to /usr/local/binThe build will automatically download cJSON (a small JSON parsing library) from GitHub using curl or wget.
The mode is selected at load time via a module parameter:
# Original WireGuard behavior
sudo modprobe wireguard crypto_mode=curve25519
# PQC mode (ML-KEM-512 + ML-DSA-44)
sudo modprobe wireguard crypto_mode=pqc
# Static mode (pre-shared master key instead of ECDH)
sudo modprobe wireguard crypto_mode=static static_key=<64-character hex key>To remove the module:
sudo modprobe -r wireguardInstead of the traditional wg0.conf format, this implementation uses JSON configuration files. The wg-pqc tool parses these files and sends the configuration to the kernel module over a Netlink socket.
This design choice was made primarily due to the increased complexity introduced by post-quantum cryptography. Unlike the original WireGuard configuration, which is relatively flat and minimalistic, PQC requires handling additional structured parameters such as KEM selection, seeds, and extended cryptographic metadata. The traditional wg0.conf format is not well-suited for representing such hierarchical data.
Using JSON allows for:
- Structured and hierarchical configuration – clear separation between interface, peer, and cryptographic parameters
- Extensibility – new algorithms, parameters, or modes can be added without breaking the format
- Direct mapping to Netlink API – simplifies serialization of complex data structures between userspace and kernel space
- Improved maintainability – the configuration format mirrors the internal data structures of the modified WireGuard implementation
- Ease of parsing and tooling – JSON is widely supported and easily processed by automation tools
This approach is particularly suitable for a research prototype that extends WireGuard beyond its original minimalist design and introduces experimental features such as runtime-selectable cryptographic modes.
# Generate a WireGuard private key
wg-pqc genkey > private.key
# Derive public key from private key
wg-pqc pubkey < private.key
# Generate a PQC seed (64 bytes by default)
wg-pqc genseed > pqc_seed.txt
# Generate a longer seed
wg-pqc genseed 128 > pqc_seed.txt{
"interface": {
"name": "wg0",
"id": "server-01",
"listen_port": 51820,
"crypto": {
"kem": "mlkem512",
"pqc_seed": "<64-byte hex string>"
},
"addresses": ["10.0.0.1/24"]
},
"peers": [
{
"id": "client-rpi",
"crypto": {
"kem": "mlkem512",
"pqc_seed": "<64-byte hex string>"
},
"endpoint": "192.168.1.100:51820",
"allowed_ips": ["10.0.0.2/32"],
"persistent_keepalive": 25
}
]
}Supported KEM algorithms: mlkem512, mlkem768, mlkem1024
# Apply config to a specific interface
sudo wg-pqc setconf wg0 config.json
# Apply config with interface name defined inside the JSON
sudo wg-pqc setconf config.json
# Show current configuration
wg-pqc show wg0| Command | Description |
|---|---|
setconf [interface] <config.json> |
Apply JSON configuration to an interface |
genseed [length] |
Generate a PQC seed (default 64 bytes, max 128) |
show <interface> |
Show current interface configuration |
help |
Show usage |
- x86_64: AWS EC2 t2.micro, Ubuntu, kernel 6.x
- ARM64: Raspberry Pi 3B+ (Raspberry Pi OS Bookworm 64-bit)
The build process is identical on both architectures. Only the LEANCRYPTO_DIR path in the Makefile needs to reflect the local leancrypto directory.
This implementation is a research prototype and has not undergone a formal security audit.
In particular:
- Post-quantum key handling in kernel space may be vulnerable to side-channel attacks
- The seed-based key derivation model is experimental
- The protocol modifications deviate from the original WireGuard security design
Do not use this implementation in production or to protect sensitive data.
- Author: Bc. Branislav Nohaj
- Institution: Technical University of Košice, Faculty of Electrical Engineering and Informatics
- Programme: Cybersecurity
- Supervisor: doc. Ing. Martin Tomášek, PhD.
- Consultant: Ing. Peter Popovec
- Year: 2026
- Email: branislav.nohaj@gmail.com
- GitHub: open an issue or discussion in this repository