OPNsenseXXI · WireGuardWireGuard
WireGuard concepts and keys — the cryptographic identity of a peer
What you'll learn
- Explain WireGuard architecture — kernel implementation, fixed cipher suite, peers and endpoints
- Generate and manage WireGuard key pairs on OPNsense and on clients
- Describe how a WireGuard handshake authenticates a peer using public keys
- Identify the operational discipline for key rotation and revocation
- Read a WireGuard interface summary and identify peer state from the key
Prerequisites
Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14
WireGuard is the simplest of the three VPN protocols OPNsense supports, and the simplicity is the security model. There is no cipher negotiation. There is no certificate authority. There is no Phase 1, Phase 2, or proposal list. There are peers, identified by public keys, communicating over UDP. Every WireGuard implementation in the world uses the same ciphers — Curve25519 for the handshake, ChaCha20-Poly1305 for the bulk data — and there are no options to misconfigure.
This lesson covers WireGuard architecture, the cryptographic key model, how a handshake authenticates a peer, and the operational discipline for managing WireGuard keys on OPNsense.
Architecture
WireGuard is implemented in the FreeBSD kernel (wireguard(4) on FreeBSD, wireguard.ko loaded as a module) and exposed through the OPNsense WireGuard plugin. The implementation is small — the protocol is ~4,000 lines of code — because the design philosophy is that complexity is the enemy of security, and the only way to audit cryptographic code is to make it small enough to read.
The protocol has three moving parts:
- Interface. A WireGuard interface (
wg0,wg1, …) behaves like any other virtual interface: it has an IP address, an MTU, and a list of peers. - Peers. Each peer is identified by a public key. The peer has an AllowedIPs list (which subnets the peer is authorised for), an optional endpoint (where to send packets when the peer is not behind NAT), and persistent state (the last known endpoint, the handshake timestamp, the data counters).
- Handshake. When the firewall wants to send a packet to a peer (or the peer initiates), a 1-RTT handshake authenticates both endpoints using their public keys and establishes fresh session keys for the bulk encryption. The handshake uses Curve25519, a fast and modern elliptic-curve Diffie-Hellman function.
There is no negotiation of any kind. The firewall knows the peer’s public key; the peer knows the firewall’s public key; they perform a handshake; the tunnel is up. The whole configuration fits on one screen.
The key model
WireGuard uses Curve25519 key pairs. Each endpoint has:
- A private key (32 bytes, ~256 bits of entropy). Never leaves the device. Used to authenticate the handshake.
- A public key (32 bytes, derived from the private key). Shared with peers. Used by the peer to verify the handshake.
The keys are base64-encoded for display. A typical WireGuard key looks like:
aGVsbG93b3JsZDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG
The key generation is fast and non-interactive — wg genkey produces a private key in microseconds. There is no CA, no certificate request, no signing step. The operator generates a key pair on each endpoint and copies the public key to the peers that need to communicate with it.
$ wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key && cat /etc/wireguard/private.key /etc/wireguard/public.keyaGVsbG93b3JsZDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG
bXlrbm9wcXV4eXowMTIzNDU2Nzg5YWJjZGVmZ2hpamtsbW5vcHF1cnN0dXZ3eHl6QUI=Illustrative output
The operational discipline for key management:
- Private keys never leave the device. Not in configuration backups, not in Git, not in monitoring outputs. The public key is what crosses the network.
- Public keys are shared out-of-band. Configuration management (Ansible, manual file copy, internal CA-equivalent process) distributes public keys to peers. There is no PKI; the operator is the CA.
- Key rotation is operational, not automatic. A compromised key requires the operator to generate a new key pair on the affected endpoint, distribute the new public key, and update every peer that references the old public key.
Peer authentication and the handshake
When the firewall wants to send a packet to a peer, it performs a 1-RTT (one round-trip time) handshake:
- The firewall generates an ephemeral Curve25519 key pair.
- The firewall computes a shared secret from its ephemeral private key and the peer’s static public key (and mixes in both static keys).
- The firewall sends a handshake initiation message containing its ephemeral public key and a MAC of the message.
- The peer receives the initiation, generates its own ephemeral key pair, computes the same shared secret, and sends a handshake response.
- Both endpoints derive fresh session keys from the shared secret and begin encrypting.
The handshake authenticates both endpoints: the peer proves knowledge of its static private key by being able to compute the same shared secret; the firewall proves the same. An attacker who does not hold the private key cannot complete the handshake, and the cryptographic properties of Curve25519 mean the handshake is forward-secret — compromising one session key does not compromise past or future sessions.
The handshake is silent on the wire: a WireGuard peer that is not sending traffic does not exchange handshake messages. The handshake is triggered by an outgoing packet. To keep the connection alive through NAT (which times out idle UDP mappings), WireGuard sends a keepalive packet every 25 seconds by default.
Reading the WireGuard interface state
The wg show command prints the state of every WireGuard interface, including the public keys of all peers, the timestamps of the last handshake, the data counters, and the current endpoint (if known).
$ wg show wg0interface: wg0
public key: serverpubkey0123456789abcdefghij=
private key: (hidden)
listening port: 51820
peer: clientpubkeyA==
endpoint: 203.0.113.50:51234
allowed ips: 10.0.0.50/32
latest handshake: 12 seconds ago
transfer: 1.24 MiB received, 348.2 KiB sent
peer: clientpubkeyB==
allowed ips: 10.1.0.0/24
latest handshake: 3 minutes, 45 seconds ago
transfer: 245.8 MiB received, 78.4 MiB sentIllustrative output
The output tells the operator four things:
- Identity — the public key. The peer is whoever holds the matching private key.
- Reachability — the endpoint (if present). A peer with no endpoint is behind NAT and waiting for an incoming packet.
- Recency — the latest handshake. A peer whose handshake is days old is not currently connected.
- Volume — the transfer counters. The data exchanged since the peer was added.
Operational discipline
Three operational disciplines matter for WireGuard keys:
-
Private key storage. Private keys are 32 bytes of entropy that authenticate the endpoint. They must be stored with the same care as any other credential — file permissions 600 or 400, encrypted at rest where the platform supports it, never in world-readable locations, never in version control.
-
Public key distribution. Public keys are configuration: they go into the peer’s WireGuard configuration as the value of
PublicKey. The distribution channel should be authenticated (a configuration management tool, a peer-to-peer file copy over SSH, an internal API). Distributing public keys over an unauthenticated channel allows an attacker to substitute their own public key and impersonate a peer. -
Key rotation. Compromised keys must be rotated. The rotation process is: generate a new key pair on the affected endpoint, distribute the new public key, update the peers that reference the old key, restart the WireGuard interface on every device that references the key. The OPNsense WireGuard plugin stores the key on the firewall; rotating requires updating each peer that connects to the firewall.
Summary
- WireGuard is a kernel-mode VPN with a fixed cipher suite, peer-to-peer authentication via public keys, and minimal configuration surface.
- Each peer has a Curve25519 key pair. The private key never leaves the device; the public key is shared with peers.
- The 1-RTT handshake authenticates both endpoints using their public keys and produces fresh session keys. There is no negotiation.
wg showis the operator’s window into WireGuard state — public keys, endpoints, handshake timestamps, and data counters.- Private keys must be protected like any credential; public keys are configuration that must be distributed over an authenticated channel.
Knowledge check · 4 questions
Q1. WireGuard uses Curve25519 for the handshake. What is the operational consequence of this design choice?
Q2. A WireGuard public key is derived from the private key by Curve25519 scalar multiplication, and only the public key is ever shared with peers.
Q3. Which of the following are valid operational disciplines for WireGuard key management? Select all that apply.
Q4. A WireGuard peer's latest handshake is 8 hours old. What does this tell the operator?
Passing score: 75%. Answers are checked in this browser.