Skip to content
Key Schedule
step 1/5

Reading — step 1 of 5

Read

~1 min readCryptography & Handshake

Key Schedule

After Noise handshake, both sides derive multiple keys via HKDF-style chains.

WireGuard's chain:

chaining_key = HASH(b"WireGuard v1 zx2c4 [email protected]")
hash = HASH(chaining_key || init_data)

# Each DH op contributes:
chaining_key, key = HKDF(chaining_key, dh_output, 2)
hash = HASH(hash || ephemeral_pub)

# Send sub-message; chain hash includes message
hash = HASH(hash || encrypted_field)

Initiator and responder follow same chain → same final state.

Final session keys:

  • Sending key (initiator → responder): from chain.
  • Receiving key (responder → initiator): from chain (different derivation).
python

Each direction has independent counter starting at 0.

Each AEAD operation:

  • nonce = counter (8 bytes) padded to 12 bytes.
  • aad = b"".
  • key = sending_key or receiving_key.

Forward secrecy: the chain depends on EPHEMERAL keys (generated per-handshake). After the handshake completes, ephemeral private keys are deleted. Even if static keys leak later, past sessions remain confidential.

Identity hiding: in Noise_IK, the initiator's static pub key is encrypted under a key derived from the responder's static pub. So passive observers can't see who's connecting.

Rekeying:

  • WireGuard: every 2 minutes OR every 64 GB transferred OR every 2^60 messages.
  • Generates fresh ephemeral keys. Keeps cryptographic margin.

Replay defense:

  • Counters are ever-increasing.
  • Receiver maintains anti-replay window.
  • Old session keys discarded after rekey.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…