WireGuard · Kryptografie

🤝Der Handshake – Noise_IKpsk2

Zwei Nachrichten (148 und 92 Byte), vier Diffie-Hellman-Operationen, ein Pre-Shared Key – danach haben beide Seiten zwei frische symmetrische Schlüssel. Der vollständige Protokollname lautet Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s.
Noise_IK

Handshake-Muster: Initiator kennt den statischen Schlüssel des Responders (K), überträgt seinen eigenen sofort (I).

psk2

Pre-Shared Key wird nach der zweiten Nachricht eingemischt (Modifier „psk“ an Position 2).

25519

Diffie-Hellman auf Curve25519 (X25519), 32-Byte-Schlüssel.

ChaChaPoly

AEAD ChaCha20-Poly1305 (RFC 7539) mit 16-Byte-Tag; im Cookie Reply XChaCha20-Poly1305 mit 24-Byte-Nonce.

BLAKE2s

Hash (32 Byte), keyed MAC (16 Byte) und HKDF auf Basis von HMAC-BLAKE2s.

🐞Schritt für Schritt (Debugger)

Mit ◀ ▶ oder den Pfeiltasten durch den Handshake. Rechts wächst das Sequenzdiagramm, unten die Byte-Karte der gerade entstehenden Nachricht.
Schritt 1/10
beide

Ausgangslage: statische Schlüssel sind bekannt

C ≔ HASH(CONSTRUCTION)
H ≔ HASH(C ‖ IDENTIFIER)
H ≔ HASH(H ‖ Sᵣ_pub)

Beide Seiten haben die öffentlichen Schlüssel der Gegenseite vorab erhalten (in der wg0.conf). Das ist das „K“ in IK: der Initiator kennt den Responder. Der Initiator startet den Chaining-Key C und den Hash H – H protokolliert das gesamte Gespräch (Transkript) und fließt als Associated Data in jede Verschlüsselung ein.

InitiatorResponderTyp 1 · Handshake Initiation148 ByteTyp 2 · Handshake Response92 ByteTyp 4 · Transport Data32 Byte
🅸 Initiator weiß
  • Sᵢ (eigenes Paar)
  • Sᵣ_pub (aus Konfig)
  • PSK (optional, sonst 32 × 0)
  • C, H
🆁 Responder weiß
  • Sᵣ (eigenes Paar)
  • Sᵢ_pub (aus Konfig)
  • PSK (optional, sonst 32 × 0)

🧱Alle vier Nachrichtentypen

Jede Nachricht beginnt mit 1 Byte Typ + 3 Null-Bytes. Verschlüsselte Felder sind immer 16 Byte (Poly1305-Tag) länger als ihr Klartext.
Typ 1 · Handshake Initiation (Handshake-Einleitung)
148 Byte
message_type1
reserved_zero3
sender_index4
unencrypted_ephemeral32
encrypted_static48
encrypted_timestamp28
mac116
mac216
0 message_type1Nachrichtentyp = 1
1 reserved_zero3drei Null-Bytes (Ausrichtung auf 4 Byte)
4 sender_index4zufälliger Index des Initiators (für spätere Zuordnung)
8 unencrypted_ephemeral32flüchtiger öffentlicher Schlüssel Eᵢ (unverschlüsselt)
40 encrypted_static48statischer öffentl. Schlüssel Sᵢ: 32 + 16 Tag = 48
88 encrypted_timestamp28TAI64N-Zeitstempel: 12 + 16 Tag = 28
116 mac116MAC mit HASH(LABEL_MAC1 ‖ Sᵣ_pub) über Bytes 0…115
132 mac216Null – oder MAC mit Cookie über Bytes 0…131 (unter Last)
Typ 2 · Handshake Response (Handshake-Antwort)
92 Byte
message_type1
reserved_zero3
sender_index4
receiver_index4
unencrypted_ephemeral32
encrypted_nothing16
mac116
mac216
0 message_type1Nachrichtentyp = 2
1 reserved_zero3drei Null-Bytes (Ausrichtung auf 4 Byte)
4 sender_index4zufälliger Index des Responders
8 receiver_index4= sender_index aus der Initiation
12 unencrypted_ephemeral32flüchtiger öffentlicher Schlüssel Eᵣ
44 encrypted_nothing16leerer Klartext: nur das 16-Byte-Tag (beweist Schlüsselbesitz)
60 mac116MAC mit HASH(LABEL_MAC1 ‖ Sᵢ_pub) über Bytes 0…59
76 mac216Null – oder MAC mit Cookie über Bytes 0…75
Typ 3 · Cookie Reply (Cookie-Antwort)
64 Byte
message_type1
reserved_zero3
receiver_index4
nonce24
encrypted_cookie32
0 message_type1Nachrichtentyp = 3
1 reserved_zero3drei Null-Bytes (Ausrichtung auf 4 Byte)
4 receiver_index4sender_index der abgewiesenen Nachricht
8 nonce2424 zufällige Bytes für XChaCha20-Poly1305
32 encrypted_cookie3216-Byte-Cookie + 16 Tag = 32
Typ 4 · Transport Data (Datenpaket)
16 B Kopf + Nutzlast + 16 B Tag
message_type1
reserved_zero3
receiver_index4
counter8
encrypted_encapsulated_packetn
0 message_type1Nachrichtentyp = 4
1 reserved_zero3drei Null-Bytes (Ausrichtung auf 4 Byte)
4 receiver_index4Index der Sitzung beim Empfänger
8 counter864-Bit-Zähler, zugleich Nonce für ChaCha20-Poly1305
16 encrypted_encapsulated_packetvar.inneres IP-Paket, auf Vielfache von 16 gepolstert, + 16 Byte Tag

⏱️Timer und Grenzwerte

KonstanteWertBedeutung
Rekey-After-Messages2⁶⁰ Nachrichtendanach neuer Handshake
Reject-After-Messages2⁶⁴ − 2¹³ − 1 NachrichtenSitzungsschlüssel wird hart verworfen
Rekey-After-Time120 sInitiator startet nach 2 Minuten einen neuen Handshake
Reject-After-Time180 sSchlüssel älter als 3 Minuten werden nicht mehr benutzt
Rekey-Attempt-Time90 sso lange werden Handshakes wiederholt
Rekey-Timeout5 sWartezeit bis zur Wiederholung einer Initiation (+ Jitter)
Keepalive-Timeout10 spassives Keepalive, wenn nur empfangen wurde
Konstanten im Handshake
CONSTRUCTION = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
IDENTIFIER = "WireGuard v1 zx2c4 Jason@zx2c4.com"
LABEL_MAC1 = "mac1----" · LABEL_COOKIE = "cookie--"
Stumm gegenüber Fremden
Ohne gültiges mac1 (das die Kenntnis des öffentlichen Schlüssels beweist) antwortet WireGuard auf nichts. Ein Portscan sieht einen geschlossenen UDP-Port.
Identitätsschutz
Der statische öffentliche Schlüssel des Initiators wird nur verschlüsselt übertragen. Ein Mitschnitt verrät, dass jemand verbindet, aber nicht wer – zum Entschlüsseln bräuchte man den privaten Schlüssel des Responders.