このページの目次

WireGuard プロトコル

WireGuard は VPN プロトコルを約 4000 行のコードにまで簡素化しました。Noise プロトコルフレームワークによる鍵交換、ChaCha20+Poly1305 による AEAD 暗号化、Cryptokey Routing によって公開鍵と許可された IP を紐付けます。ステートマシンも交渉もなく、暗号化のみが存在します。

概要

WireGuard(Jason Donenfeld, 2016)は VPN の簡潔さを再定義しました。コード行数は約 4000 行で、OpenVPN(約 70,000 行)や IPsec(約 400,000 行)と比較して極めて軽量です。設計哲学は「交渉しない」ことです。暗号スイートは固定(Curve25519 + ChaCha20-Poly1305)であり、ポートスキャンは検知できません(未認証のパケットには応答しないため)。接続は IP ではなく公開鍵によって識別され、透過的なローミングをサポートします。WireGuard は Linux 5.6 でメインラインにマージされ、Linux 上で最速の VPN 実装となりました。Noise IK ハンドシェイクは 1-RTT で鍵交換を完了し、120 秒ごとに自動で再鍵を行うことで前方秘匿性を保証します。

設計原則

WireGuard はレイヤー 3 の VPN であり、鍵交換と暗号化を極限まで簡素化することを目標としています。

  • 無交渉: 暗号スイートは固定です(Curve25519 + ChaCha20-Poly1305 + BLAKE2s + SipHash24 + HKDF)。暗号スイートの交渉を行わないため、ダウングレード攻撃のリスクがありません。
  • 無接続: 「接続確立」状態が存在しません。有効な認証済みパケットを受信した時点でセッションが確立され、未認証のパケットには応答しません(ポートスキャンでは検出できません)。
  • 沈黙: 能動的には一切のパケットを送信しません(PersistentKeepalive が設定されていない限り。NAT 穿透が必要な場合にのみ使用します)。
  • ローミング: エンドポイントの IP は可変です。有効なパケットを受信するたびに、ピアのエンドポイントを更新します。

Noise IK ハンドシェイク

WireGuard は Noise Protocol Framework の Noise_IK モードを使用します(I=Initiator の静的鍵、K=Responder の静的鍵)。

双方で事前に共有される鍵:
  I_s: Initiator の静的秘密鍵 (Curve25519)
  I_pub: Initiator の静的公開鍵(相手が認識し、設定済み)
  R_s: Responder の静的秘密鍵
  R_pub: Responder の静的公開鍵(相手が認識し、設定済み)

Initiator → Responder (Handshake Initiation, 148 バイト):
  type = 1 (4 バイト)
  sender_index = (4 バイト, ランダム)
  unencrypted_ephemeral (E_i_pub, 32 バイト)   ← DH ephemeral
  encrypted_static (AEAD):
    I_pub (32 バイト)                          ← Initiator の身元
    timestamp (12 バイト)                      ← TAI64N, リプレイ攻撃防止
  mac1 (16 バイト): MAC(Responder 公開鍵, メッセージ)
  mac2 (16 バイト): Cookie Reply を受信済みの場合は cookie value; それ以外は 0

Responder → Initiator (Handshake Response, 92 バイト):
  type = 2 (4 バイト)
  sender_index = (4 バイト)
  receiver_index = Initiator の sender_index
  unencrypted_ephemeral (E_r_pub, 32 バイト)   ← DH ephemeral
  encrypted_nothing (AEAD): 空 (0 バイト)       ← ただし AEAD タグで完全性を検証
  mac1 (16 バイト)
  mac2 (16 バイト): 0

ハンドシェイク完了後、双方は以下を実行します:
  1. DH(E_i_priv, E_r_pub) = DH(E_i_pub, E_r_priv) = DH(E_r_priv, E_i_pub) = shared
  2. shared + 公開鍵から HKDF を用いて派生:
    - 対称鍵 (T_send, T_recv)
    - 2^60 個のパケットごとに鍵を変更(1Gbps で約 280,000 年)

鍵ローテーション (Rekey)

Handshake Initiation のたびに新しい ephemeral DH 鍵ペアが生成され、新しいセッション鍵が作成されます。デフォルトでは 120 秒間のトラフィックがないと、新しい Handshake Initiation が送信され(rekey)、新しいセッション鍵が生成されます。古いセッションのパケットは、新しいセッションの鍵が使用されるまでの短い遷移ウィンドウの間、依然として受信可能です。

タイマー状態マシン

ピアごとにタイマーが存在:
  - handshake_initiation_interval: REKEY_TIMEOUT + ジッター(デフォルト 120 秒後)
    → 最後のハンドシェイクから 120 秒以上経過している場合 → 新しいハンドシェイクを送信 → 新しいセッション鍵を生成
  - persistent_keepalive_interval: 定期的に keepalive を送信(空メッセージ、NAT リバインド検出用)
    → デフォルトは 0(設定されていない限り送信しない)
  - retry_interval: ハンドシェイクのリトライに対して指数バックオフ(5s, 10s, 20s, ...)
  - zero: 送信するデータがない場合 → 何もしない(沈黙、パケットを送信しない)

Cookie Reply(DoS 防止): 偽造された可能性のある Handshake Initiation(mac1 の検証は通過したが mac2 が空)を受信した場合、Responder は Cookie Reply メッセージを送信します。相手は后续の Handshake でこの cookie(mac2)を含める必要があります。これにより、相手自身が主張する IP への到達性を証明します。

データプレーン: 転送データ

Transport Data (type=4, 最小 32 バイト):
  receiver_index (4 バイト): 相手の sender_index → ピアの高速検索
  counter (8 バイト): 単調増加、メッセージごとに +1
  encrypted_encapsulated_packet (AEAD):
    IP パケット(IPv4 または IPv6)— 生パケット全体が暗号化される
  → AEAD タグ (16 バイト) = Poly1305 MAC

/proc/config

# ピア情報の表示:
wg show

# ピアの追加:
wg set wg0 peer <pubkey> allowed-ips 10.0.0.2/32 endpoint 1.2.3.4:51820

# ハンドシェイクの経過時間:
wg show wg0 latest-handshakes

参考

  • ホワイトペーパー: wireguard.com/papers/wireguard.pdf
  • Noise: noiseprotocol.org/noise.html (IK パターン)
  • ソースコード⁠: drivers/net/wireguard/(約 4000 行): noise.[ch](ハンドシェイク)、receive.csend.c

キーワード: WireGuard, Noise IK, Curve25519, ChaCha20-Poly1305, timer, rekey, roaming, cookie reply