WireGuard: The Beauty of Simplicity
This article runs about 4,300 characters; 10 minutes for a casual read, 30 minutes for a careful one.
Recently, at an internal BBL session, I gave a talk on WireGuard. WireGuard (WG hereafter), the standard-bearer of the new generation of VPNs, is probably no stranger to many of you in tech. Like any other VPN technology, we can use it to build a secure channel between a home network and a corporate network, and thereby reach the data and applications on the “intranet.”
Before diving into WG itself, let’s abstract the general requirements of a VPN:
- Security: data between two private networks must travel safely across an unsafe network (the Internet, say).
- Authenticity: the visitor is a legitimate user, and the visitor is reaching the right network.
- Efficiency: turning on the VPN must not noticeably slow down access, and the tunnel must come up fast.
- Stealthiness: a third party cannot easily sniff out the existence of the gateway.
- Accessibility: it is easy to configure, easy to turn on, easy to turn off.
For the ability to move data safely across an unsafe network, we must thank Martin E. Hellman, Bailey W. Diffie, and Ralph C. Merkle. Their patent Cryptographic apparatus and method introduced the DH algorithm that later became ubiquitous, used for key exchange.
The algorithm itself exploits the properties of congruence (modular arithmetic) and the commutativity of multiplication; the procedure is simple — see Wikipedia if you’re curious. The ECDH that WG uses is a variant of DH, employing elliptic curves to boost both performance and security:

With DH, the two ends of a network can negotiate — over an insecure network — the key that will encrypt the data they transmit. After that, the data stream can be efficiently symmetrically encrypted with that key.
Security is now solved. So how do we solve authentication of the two ends? Today there are two general answers:
- Pre-shared key
- Certificate
For example, when we visit China Merchants Bank’s website, the browser verifies the bank’s certificate to confirm that the site we’re reaching is indeed the site we intended to reach. And when a company’s headquarters network needs to talk to a branch-office network, the two can pre-configure each other’s public keys and then verify each other via digital signatures — a variant of the pre-shared key (a pure pre-shared key lacks forward secrecy and should almost never be used for communication).
With security and identity authenticity solved, the most important problems of a VPN are solved. The VPN solutions we use today — whether IPSec VPN, which works at the network layer, or SSL/TLS/OpenVPN, which works at the session layer — never really escape the algorithms described above when it comes to key exchange and authentication. They are as complex as they are largely because of configuration handling, cipher-suite negotiation, and all manner of compatibility issues. The protagonist of this article, WG, is not particularly innovative algorithmically, yet by organizing its requirements cleverly and taking a different path in implementation, it achieves a kind of breathtaking simplicity.
Here’s a comparison of code size:

WG’s kernel implementation is only 4k lines! It is exquisite beyond exquisite! (It’s a bit disrespectful to say so, but by comparison OpenVPN and StrongSwan look like the output of an Indian outsourcing shop that bills by the line, while WG is the work of a real programmer.) Linus himself cannot praise WG enough. In an email dated August 2, 2018, he wrote:
Btw, on an unrelated issue: I see that Jason actually made the pull request to have wireguard included in the kernel.
Can I just once again state my love for it and hope it gets merged soon? Maybe the code isn’t perfect, but I’ve skimmed it, and compared to the horrors that are OpenVPN and IPSec, it’s a work of art.
Linus
Understand that Linus’s usual style when reviewing code is more like this (Mauro is a kernel maintainer):
“It’s a bug alright – in the kernel. How long have you been a maintainer? And you still haven’t learnt the first rule of kernel maintenance?
“Shut up, Mauro. And I don’t ever want to hear that kind of obvious garbage and idiocy from a kernel maintainer again. Seriously.
So getting Linus to “state my love” is harder than ascending to heaven. So let us, in a spirit of pilgrimage, kneel and learn how WG pulls it off — this way of thinking about products and writing code deserves our devoted study!
The Concept of the WG Interface
Let’s start with concepts.
Many people building products don’t much care about clearly explaining the many old and new concepts that exist in the product — and they especially dare not invent new ones. This is a profoundly wrong approach. When we begin architecture design, we should sort out all the product’s concepts clearly, and when existing concepts fail to describe some part of the product well, we should have the courage to create new concepts so that the description becomes complete enough. Concepts are the basis of communication between engineers, and between engineers and the outside world; communicating through concepts everyone agrees on is more precise and more efficient. For example, once I’ve given ECDH the concept of “a variant of DH that uses elliptic curves,” the next time I mention ECDH I needn’t laboriously re-explain it. Once we create a new concept, we can attach many attributes to it, distinguishing it from the others.
WG first defines a very important concept — the WireGuard Interface (wgi hereafter). Why do we need a wgi? Why don’t existing tunnel interfaces fit the bill? A wgi is a special interface that:
- has its own private key (curve25519)
- has a UDP port on which it listens for data
- has a set of peers (peer being another important concept), each peer authenticated by its public key
By defining such a new interface, wgi sets itself apart from ordinary tunnel interfaces. With this definition in place, mounting other data structures and sending/receiving data all become clean and clear.
Let’s look at WG’s interface configuration:
[Interface]
Address = 10.1.1.1/24
ListenPort = 12345
PrivateKey = blablabla
[Peer]
PublicKey = IWNVZYx0EacOpmWJq6lE8RfcFBd8EeUliOi+uYKQfG8=
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = 1.1.1.1:54321
The initiator and responder of WG’s VPN tunnel are peers, so unlike ordinary VPNs there is no client/server or spoke/hub distinction. The configuration is likewise symmetric.
In this configuration we learn more about the concept of a peer: it is WG’s counterpart node, with a statically configured public key, a whitelist of the networks behind it (AllowedIPs), and the peer’s address and port (which isn’t strictly necessary, and may change automatically as the network roams).
In just nine lines of configuration we’ve described the simplest possible VPN network. There’s no endless certificate configuration, no hundred-line wall of incomprehensible gibberish, no CA to set up. If you’ve ever had the misfortune to configure an IPSec VPN or OpenVPN, you’ll sigh and say that simplicity really is the primary productive force.
From a data-structure perspective, under a wgi hang a hash table of peers and a hash table of key indices (key_index). From the key_index in an incoming packet we can immediately locate the peer, and under each peer we store the endpoint state, the handshake protocol state, and the keypairs (three sets: the key currently in use, the key used before the last rekey, and the next key to be used after the rekey) — and each keypair again comprises two directional keypairs, one for receiving and one for sending.
When the wgi interface is enabled (wg-quick up wg0), the wgi is initialized and its associated peers are created; conversely (wg-quick down wg0), the wgi stops running and the associated peers are deleted. The data structures are laid out with complete clarity.
The Encrypted Tunnel Negotiation Process
WG’s simple design also shows in how the encrypted tunnel is negotiated. It uses the Noise Protocol Framework to build its negotiation process. Noise is an elegantly designed framework for constructing secure protocols; I won’t go into it here, but will write a separate article about it. WG uses Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s. From the protocol name you can probably guess that it picks curve25519 for ECDH, ChaChaPoly for symmetric encryption, and BLAKE2s for hashing. In IKE/SSL/TLS these algorithms are all negotiated by the two ends; WG figures that’s unnecessary and hardcodes them into the protocol, which drastically reduces the number of ciphers that must be supported and eliminates cipher negotiation altogether. Because both ends are configured with each other’s public keys, it can establish the tunnel in a mere 1-RTT (a single round trip), two packets. Look at the IKE protocol used by IPSec: six packets (3-RTT) in main mode, and still three packets (2-RTT) in aggressive mode. From Beijing to Seattle, 1-RTT is roughly 175 ms (cloudping.info); 2-RTT introduces very noticeable latency. For any protocol, reducing the RTT of tunnel negotiation greatly improves performance.
1-RTT also implies connectionless, since neither side acknowledges the other. Compare connection-oriented TCP (a three-way handshake to confirm a gaze connection) with connectionless UDP. Connection-oriented networks have countless advantages, but connectionless wins on simplicity — like a fish with a seven-second memory, free of the burden of past, present, and future.
For connection-oriented protocols there is generally a state table recording how far the protocol has progressed. That table is dynamically generated and easily becomes a DoS target — TCP, for instance, has suffered from SYN floods since its birth. Connectionless protocols carry no such burden: the server need not do anything special for a client’s handshake request, need not worry about packet loss (lost? just handshake again — it’s only 1-RTT), need not run timers to manage half-open connections in a connection table (there is no such table), and so on.
A WG handshake packet carries:
- unencrypted_ephemeral: the sender’s public key generated on the fly for this handshake (unencrypted, used for ECDH)
- encrypted_static: the peer’s public key, symmetrically encrypted with key1, the ephemeral key derived via ECDH from the peer’s public key and the freshly generated private key
- encrypted_timestamp: key2 derived via ECDH from the peer’s public key and one’s own private key, then mixed into key1, encrypting the current timestamp
- mac1: a hash over the peer’s public key plus the entire packet contents
The receiver first checks mac1 (a simple identity check — most hackers give up right here); if it’s wrong, the packet is dropped. Then it validates encrypted_static (the confirming look — unless you hold the private key, you give up here too), then validates encrypted_timestamp (guarding against replay, so replay attacks give up as well). Once everything checks out, the receiver can generate its own ephemeral keypair. At this point, having the peer’s ephemeral public key, it can already compute the key that will encrypt data after this negotiation. But it still needs to send a handshake response carrying its own ephemeral public key so that the sender can compute the same key:
- unencrypted_ephemeral: the receiver’s public key generated on the fly for this handshake (unencrypted, used for ECDH)
- mac1: a hash over the peer’s public key plus the entire packet contents
Now both ends have each other’s ephemeral public keys; combined with their own ephemeral private keys, they can run ECDH + HKDF (a method for turning the DH result into symmetric keys) to derive the two directional symmetric keys for this handshake.
If a packet is lost along the way — the receiver never gets the sender’s handshake request, or the sender never gets the response — the whole thing simply starts over. It’s 1-RTT either way, so nothing is wasted.
WG also gives full consideration to stealthiness here: the receiver simply drops any invalid handshake it receives (a peer I don’t recognize, a retransmission, whatever). From the sender’s point of view, its handshake packets vanish into a black hole, so unless an attacker can get the WG gateway to authorize their public key as a peer, they have essentially no way to sniff out the receiver’s existence. In other VPN protocols, the tunnel-establishment phase — IPSec’s IKE, OpenVPN’s SSL/TLS — can both be detected by sniffing.
Sending and Receiving Data Packets
Once the keys exist, handling the user’s data packets is easy. The logic is so simple and clear that a few lines cover it:
- Sending:
- User space: the application sends a packet destined for the VPN peer’s network.
- Kernel: the kernel consults the routing table, finds the packet should go out via the wg0 interface, and hands it to WG.
- WG: using the destination address, look up from the interface configuration which peer to send to, then encrypt the packet with the key previously negotiated with that peer (renegotiating if there is none or it has expired), and encapsulate the result in a UDP packet whose destination address and port are the peer’s endpoint (the packet carries the key_index).
- Receiving:
- Kernel: the UDP port is the one WG is listening on, so the packet is passed to WG (WG’s recv gets it).
- WG: find the corresponding key in the hash table from the packet’s key_index and decrypt (not directly decrypt — the packet is placed in a decryption queue; this is a small design trick in networking systems).
- WG: check whether the decrypted original packet falls within the peer’s allowed IP list; if so, hand it to the kernel. Note that which peer this packet belongs to is also derived from the key_index.
- Kernel: look up the routing table by the original packet’s destination address and send it out.

Too Dry? Let’s Get Our Hands Wet
In the BBL, I ran this demo: set up a WG VPN from my laptop to a machine on DigitalOcean, then issue an HTTP GET, and have the server return some text containing “hello world.” Here’s the Wireshark capture, with a few annotations from me:


Further Reading
- WireGuard Protocol: https://www.wireguard.com/protocol/
- Noise Protocol: https://noiseprotocol.org/
- Authenticated Encryption with Associated Data (AEAD) algorithm - RFC7539
- HKDF: https://tools.ietf.org/html/rfc5869
- The DH algorithm patent: https://patents.google.com/patent/US4200770
- WireGuard source code: https://github.com/WireGuard/WireGuard
- Linus’s email: https://lists.openwall.net/netdev/2018/08/02/124
A Moment of Reflection
As a veteran who spent years wallowing in network and security protocols, I found WireGuard’s impact on me all-encompassing. It landed like a hammer blow on my head: if you know how to make the right trade-offs and strip away the ceremonial excess, even something as complex as a VPN protocol can be this fresh and elegant; a simple, thoughtfully considered user interface (the config) means an easy-to-use product and a design of great wisdom disguised as simplicity; and the resulting simplicity makes everything that follows simpler. Because the interface is simple and clear, almost all data structures can be pre-generated; because the protocol itself is simple (1-RTT), renegotiating at any moment is simple too; packets lost during the handshake? So what — handshakes are fast and cheap. In the end, because it’s simple, there’s little code and no twists and turns; an engineer reasonably familiar with C and Linux development can spend an afternoon and understand the main flow — which means the code is easier to review, writing tests takes less time and reaches higher coverage more easily, mistakes are harder to make, bugs are fewer, debugging takes less time, and more time is left for the programmer to think deeply and look ahead. And of course there’s no need for 996: the time saved can be spent happily playing with wife and kids, or reading a book with three or five kindred spirits, attending a concert, or just talking. All of it is good.