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 PersistentKeepalive is 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:
wg show

# Add a peer:
wg set wg0 peer <pubkey> allowed-ips 10.0.0.2/32 endpoint 1.2.3.4:51820

# Check handshake age:
wg show wg0 latest-handshakes

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