On this page
WireGuard Protocol
WireGuard simplifies the VPN protocol to ~4000 lines of code—Noise Protocol Framework for key exchange, ChaCha20+Poly1305 for AEAD encryption, and Cryptokey Routing to bind public keys to allowed IPs. No state machine, no negotiation, just encryption.
Overview
WireGuard (Jason Donenfeld, 2016) redefines VPN simplicity—~4000 lines of code compared to OpenVPN (~70,000) and IPsec (~400,000). The design philosophy is "no negotiation": the cipher suite is fixed (Curve25519 + ChaCha20-Poly1305), port scanning is invisible (does not respond to unauthenticated packets), and connections are identified by public keys rather than IPs (supporting seamless roaming). WireGuard has been merged into the Linux mainline since version 5.6, becoming the fastest VPN implementation on Linux. The Noise IK handshake completes key exchange in 1-RTT, and automatic rekeying every 120 seconds ensures forward secrecy.
Design Principles
WireGuard is a Layer 3 VPN, designed to simplify key exchange and encryption to the extreme:
- No negotiation: The cipher suite is fixed (Curve25519 + ChaCha20-Poly1305 + BLAKE2s + SipHash24 + HKDF). No cipher suite negotiation means no downgrade attacks.
- Connectionless: There is no "connection established" state—a session is established upon receiving a validly authenticated packet, and unauthenticated packets are ignored (making port scanning ineffective).
- Silent: It does not actively send any packets (unless
PersistentKeepaliveis configured, which is only needed for NAT traversal). - Roaming: Endpoint IPs can change—the peer endpoint is updated every time a valid packet is received.
Noise IK Handshake
WireGuard uses the Noise_IK mode of the Noise Protocol Framework (I=static key for Initiator, K=static key for Responder):
Pre-shared between both parties:
I_s: Initiator's static private key (Curve25519)
I_pub: Initiator's static public key (known and configured by the peer)
R_s: Responder's static private key
R_pub: Responder's static public key (known and configured by the peer)
Initiator → Responder (Handshake Initiation, 148 bytes):
type = 1 (4 bytes)
sender_index = (4 bytes, random)
unencrypted_ephemeral (E_i_pub, 32 bytes) ← DH ephemeral
encrypted_static (AEAD):
I_pub (32 bytes) ← initiator identity
timestamp (12 bytes) ← TAI64N, prevents replay attacks
mac1 (16 bytes): MAC(Responder pubkey, msg)
mac2 (16 bytes): If a cookie reply was received → cookie value; otherwise zero
Responder → Initiator (Handshake Response, 92 bytes):
type = 2 (4 bytes)
sender_index = (4 bytes)
receiver_index = initiator's sender_index
unencrypted_ephemeral (E_r_pub, 32 bytes) ← DH ephemeral
encrypted_nothing (AEAD): empty (0 bytes) ← but AEAD tag verifies integrity
mac1 (16 bytes)
mac2 (16 bytes): 0
After completing the handshake, both parties:
1. DH(E_i_priv, E_r_pub) = DH(E_i_pub, E_r_priv) = DH(E_r_priv, E_i_pub) = shared
2. Derive from shared + pubkeys using HKDF:
- Symmetric keys (T_send, T_recv)
- Keys are rotated after every 2^60 packets (approximately 280,000 years at 1Gbps)
Key Rotation (Rekey)
Each Handshake Initiation generates a new ephemeral DH key pair → new session keys. If there is no traffic for the default 120 seconds, a new Handshake Initiation is sent (rekey). Packets from the old session can still be received (until the new session's keys are put into use, during a short transition window).
Timer State Machine
Each peer has timers:
- handshake_initiation_interval: REKEY_TIMEOUT + jitter (default after 120s)
→ If the last handshake was more than 120s ago → send a new handshake → generate new session key
- persistent_keepalive_interval: Periodically send keepalives (empty messages, only for NAT rebind detection)
→ Default is 0 (do not send, unless configured)
- retry_interval: Exponential backoff (5s, 10s, 20s, ...) for handshake retries
- zero: If there is no data to send → do nothing (silent, do not send any packets)
Cookie Reply (DoS prevention): If a suspected forged Handshake Initiation is received (mac1 verification passes but mac2 is empty), the Responder sends a Cookie Reply message. The other party must include this cookie (mac2) in subsequent Handshakes. This proves the other party's reachability at the claimed IP address.
Data Plane: Transport Data
Transport Data (type=4, minimum 32 bytes):
receiver_index (4 bytes): Peer's sender_index → fast peer lookup
counter (8 bytes): Monotonically increasing, +1 for each message
encrypted_encapsulated_packet (AEAD):
IP packet (IPv4 or IPv6) — the original packet is encrypted in its entirety
→ AEAD tag (16 bytes) = Poly1305 MAC
/proc/config
# View peer information:
# Add a peer:
# Check handshake age:
References
- whitepaper: wireguard.com/papers/wireguard.pdf
- Noise: noiseprotocol.org/noise.html (IK pattern)
- Source code:
drivers/net/wireguard/(~4000 lines):noise.[ch](handshake),receive.c,send.c
Keywords: WireGuard, Noise IK, Curve25519, ChaCha20-Poly1305, timer, rekey, roaming, cookie reply