skip to content

In WireGuard's Noise IKpsk2 handshake, what does each of the two messages carry, and how does one round trip authenticate both peers?

level: middleimportance: nice to knowfreq 15%

answer

  1. the initiator already knows the responder
  2. ephemeral, encrypted static, encrypted timestamp
  3. four Diffie-Hellman results chained
  4. the pre-shared key mixed last
  5. the responder waits for first data

basics

~20 s

The initiator, already holding the responder's static key, sends an ephemeral key, its encrypted static key and an encrypted timestamp. The responder answers with its own ephemeral key. Chaining four Diffie-Hellman results proves both static keys in one round trip.

solid answer

~50 s

WireGuard runs the Noise pattern `IK` with the `psk2` modifier. `K` means the initiator already knows the responder's static public key; `I` means it sends its own static key immediately, in message one. The **handshake initiation** (type 1) carries a fresh ephemeral key, the initiator's static key encrypted under a key mixed from `es`, and a TAI64N timestamp encrypted under a key that also mixes `ss`. Only the responder's private key can open it, and only the initiator's private key could have built it. The **handshake response** (type 2) carries the responder's ephemeral key and an empty authenticated payload whose key also mixes `ee`, `se` and the optional pre-shared key. Producing it proves the responder's private key. Both sides then derive one key per direction, and the initiator can send data after one round trip. The responder sends nothing until that first data message arrives.

go deeper

for a junior

Recall that WireGuard's handshake is just two messages, initiation and response, and that it works because each side already has the other's public key.

for a middle

Explain what message one carries, which encrypted field depends on which Diffie-Hellman result, and why the responder waits for the initiator's first data message.

for a senior

Explain the handshake's defences: the per-peer timestamp against replay, mac1 keeping the gateway silent to strangers, and the cookie reply under load.

for a principal

Contrast the cost of agility: a one-round-trip handshake exists because nothing is negotiated, which is the same property that removes algorithm choice.

## Why one round trip is enough A **handshake** turns long-term identities into fresh session keys. WireGuard's takes exactly **two messages**: one from the **initiator** and one back from the **responder**. A negotiating protocol needs more. IKEv2's common case is four messages in two exchanges, because algorithms must be agreed before identities are proven. WireGuard skips that for two reasons. Its algorithms are fixed, and each side **already has the other's static public key**, configured out of band. The design comes from the **Noise protocol framework**, and the WireGuard paper fixes the instance in its construction string `Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s`. ## Reading the pattern name | Part | Meaning (Noise framework) | |---|---| | `I` | the initiator's static key is sent **immediately**, in message one, encrypted | | `K` | the responder's static key is **known** to the initiator beforehand | | `psk2` | an optional pre-shared key is mixed in at the **end of message two** | | `25519` | Curve25519 Diffie-Hellman | | `ChaChaPoly` | ChaCha20-Poly1305 authenticated encryption | | `BLAKE2s` | the hash, also used for HKDF | In Noise notation the pattern is `-> e, es, s, ss` then `<- e, ee, se`. Here `e` and `s` are ephemeral and static public keys, and a two-letter token such as `es` is a Diffie-Hellman between one side's ephemeral and the other's static key. Every result is hashed into a **chaining key**. Each encrypted field uses a key derived from everything mixed in so far. ## Message one: handshake initiation (type 1, 148 bytes) - `sender`: a random 32-bit index the initiator uses for this session (the paper compares it to IPsec's SPI); - `ephemeral`: a fresh Curve25519 public key, 32 bytes, in the clear; - `static`: the initiator's static public key, **encrypted** (32 bytes plus a 16-byte tag) under a key that depends on `es`, so only the responder can read it, which hides the initiator's identity from observers; - `timestamp`: a 12-byte **TAI64N** time, encrypted (plus 16) under a key that also depends on `ss`, the static-static result; - `mac1` and `mac2`: 16 bytes each, for denial-of-service handling. The arithmetic is 1 type byte + 3 reserved + 4 + 32 + 48 + 28 + 16 + 16 = **148 bytes**. A responder that decrypts the timestamp knows the message came from the holder of the initiator's static private key, because `ss` needs it. The paper notes this authentication is **not a signature**. WireGuard uses none. ## Message two: handshake response (type 2, 92 bytes) The responder replies with its own `sender` index, the initiator's index as `receiver`, its fresh `ephemeral` key and an **empty** encrypted payload (16 bytes of tag), plus `mac1` and `mac2`: 1 + 3 + 4 + 4 + 32 + 16 + 16 + 16 = **92 bytes**. Before encrypting, it mixes in `ee`, `se` and then the pre-shared key (32 zero bytes when none is configured). The chaining key already contains `es`, which only the responder's private key could compute, so a valid empty payload proves the responder's identity. The response is smaller than the initiation, so it cannot be used for amplification. ## After the handshake 1. Both sides derive two symmetric keys from the final chaining key, **one for each direction**, and set their nonce counters to zero. Handshake secrets are zeroed. 2. The initiator may send **transport data** (type 4) at once. 3. The responder waits until it receives that first data message. This is **key confirmation**, and the paper requires it. 4. Sessions are short-lived. The initiator starts a new handshake once a session is `Rekey-After-Time` (120 s) old, and no session is used past `Reject-After-Time` (180 s). ## What protects the handshake itself - **Replay:** the responder keeps the greatest timestamp seen per peer and discards initiations whose timestamp is not greater. Without this, a replayed initiation could make it discard a live session. - **Silence:** `mac1` is keyed with a hash of the responder's public key, and a message with an invalid `mac1` is never answered. A scanner that does not know the key gets nothing back. - **Load:** a responder under load may answer a valid-`mac1` initiation with a **cookie reply** (type 3) instead of doing Curve25519 work. The initiator proves its address by placing a MAC under that cookie in `mac2` on its next attempt.

  • Why does WireGuard's first handshake message carry an encrypted timestamp?
    Message one authenticates the initiator with no earlier round trip, so a captured copy could be replayed. A replay would make the responder generate a new ephemeral key and disrupt the live session. The responder records the greatest TAI64N timestamp per peer and discards any initiation that is not newer. The paper notes it need not be an accurate clock, only a per-peer value that keeps increasing.
  • What does the psk2 part of WireGuard's handshake add, and when is it worth configuring?
    It lets a pair of peers share an optional 256-bit symmetric key, mixed in at the end of message two. When none is set, 32 zero bytes are used. The paper's reason is long-term recording: traffic captured today stays protected even if Curve25519 is broken later. The cost is one more secret per peer pair to distribute and rotate.

saying these in an interview costs you the question

  • WireGuard's first handshake message proposes algorithms and the second authenticates.
  • A WireGuard responder can send data as soon as it has sent its handshake response.
  • WireGuard's handshake proves each static key with a digital signature.
  • WireGuard's handshake timestamp must come from an accurately synchronised clock.
  • The initiator's static public key crosses the network in clear text.