WireGuardプロトコル

このページの目次

WireGuardはUDPベースのレイヤー3暗号化トンネルであり、ピアの公開鍵と許可されたIPプレフィックスを結びつけます。アルゴリズムの選択やセッション管理インターフェースを簡素化していますが、内部にはハンドシェイク状態、キーの世代、カウンター、リプレイウィンドウ、タイマーが存在し、「ステートマシンがなく、暗号化だけである」と記述することはできません。

安全なデータプレーンを提供しますが、完全なユーザーログイン、デバイス承認、アドレス割り当て、DNS、NAT透過制御プラットフォームは含まれていません。これらの機能は設定ツールや他のシステムで補完できます。公開鍵が1対存在しても、2台のデバイスが互いに到達可能であることを意味するわけではありません。

問題重要な理解
AllowedIPsはルーティングかアクセス制御か送信は宛先に基づいてピアを選択し、受信は送信元で検証する
148バイトのハンドシェイクには何が含まれるか2つの独立した暗号化フィールドであり、1つの結合ブロックではない
120秒はトラフィックがない場合でも定期的に再ハンドシェイクするかキーの年齢、役割、送受信イベントに関連する
keepaliveはすべてのNATを透過させるか既存の状態を維持することは、自動的に到達可能なパスを確立することを意味しない
latest handshakeが最新なのに業務が不通なのはなぜか内部ルーティング、AllowedIPs、またはファイアウォールが原因の可能性もある
MTUは常に1420か外部パスとオーバーヘッドの予算に基づいて計算する必要がある

公開鍵のアイデンティティ、AllowedIPs、外部エンドポイント

ピアの公開鍵は設定された暗号化アイデンティティを表します。エンドポイントは現在UDPを送信しているIPとポートです。AllowedIPsは内部IP範囲をそのアイデンティティに関連付けます。外部アドレスは変更可能ですが、内部アドレスと公開鍵の関係はローカルポリシーによって決定されます。

AllowedIPs:送信は公開鍵を選択し、受信は内部送信元アドレスを確認する 送信:宛先IP = 10.0.0.7 最長一致 10.0.0.7/32 → Peer B 受信:Peer Aによって認証・復号済み 内部送信元IP = 10.0.0.7 Bのセッションキーを使用して送信 外部エンドポイントへの到達可能性も必要 送信元アドレスはBに帰属し、Aのなりすましを拒否 暗号化が有効でも、任意の内部送信元アドレスを許可するわけではない 例:Aは10.0.0.0/24を所有し、Bはより具体的な10.0.0.7/32を所有します。システムルーティングでパケットをインターフェースに送る必要があります。

オペレーティングシステムはまずルーティングに基づいて内部パケットをWireGuardインターフェースに渡し、インターフェースは宛先アドレスに基づいてAllowedIPs内で最長プレフィックスマッチを行い、ピアを選択します。受信側は最初に認証・復号を行い、次に内部送信元アドレスがそのピアに帰属するかを確認します。復号に成功しただけでは、そのピアが他の内部アドレスをなりすますことができることを意味しません。Cryptokey Routing

例えば、Aが10.0.0.0/24を設定し、Bが10.0.0.7/32を設定している場合:10.0.0.7宛てはBを選択し、10.0.0.8宛てはAを選択します。Aが内部パケットの送信元アドレス10.0.0.7で送信する場合、この設定ではより具体的な帰属不一致により拒否されます。AllowedIPsは、各ピアに対して独立した集合包含テストを行う単純なものではありません。

from ipaddress import ip_address, ip_network

routes = [(ip_network('10.0.0.0/24'), 'A'), (ip_network('10.0.0.7/32'), 'B')]
def owner(address):
    ip = ip_address(address)
    candidates = [(net.prefixlen, peer) for net, peer in routes if ip in net]
    return max(candidates)[1] if candidates else None

assert owner('10.0.0.7') == 'B'
assert owner('10.0.0.8') == 'A'
assert owner('10.0.1.1') is None
assert owner('10.0.0.7') != 'A'  # この設定ではAはBの内部送信元アドレスをなりすませられない
print('最長プレフィックス選択と受信元帰属モデル通過')

wgはWireGuardパラメータを持つ設定インターフェースです。アドレスとシステムルーティングは他の設定レイヤーです。wg-quickは設定に基づいてアドレスとルーティングなどの設定を補助しますが、これはプロトコルがAllowedIPsを自動的にピアにブロードキャストすることを意味するものではありません。0.0.0.0/0はIPv6の::/0をカバーするものではありません。wg、wg-quick

Aが全体の/24を許可しているのに、なぜ其中的な.7を送信できないのか?

アドレスエントリの金色の.7は、より具体的な/32によってBに割り当てられます。受信ピアをAのままに保ち、.7と.8を往復移動させると、この点で結果が変化します。これは、各ピアの許可集合を独立してチェックするのではなく、統一された最長プレフィックス帰属に由来します。

図を準備しています
Aが全体の/24を許可しているのに、なぜ其中的な.7を送信できないのか?

最長プレフィックス帰属はより広いプレフィックスをカバーします。Aによって認証されたパケットを受信した後、送信元アドレスがBに帰属する場合、拒否されるべきであり、Aの/24が含まれているかどうかだけを检查してはいけません。

Noiseハンドシェイク:固定アルゴリズムの組み合わせであり、1回だけ行うものではない

WireGuardはNoise_IKpsk2_25519_ChaChaPoly_BLAKE2s構成を採用し、静的キーと一時キーの組み合わせによって認証と方向キーを確立します。オプションのPSKは追加材料として混入されます。各接続で長い一連の暗号スイートをネゴシエートするわけではありませんが、アルゴリズムが固定されているからといって、プロトコル外に降伏や設定リスクが存在しないわけではありません。

発信者は事前に応答者の静的公開鍵を知っており、応答者は解読された発信者の公開鍵に基づいて設定されたピアを識別します。双方は公開されたアイデンティティ材料を交換するだけでよく、「双方が事前共有している」と書くことは、お互いが相手の静的秘密鍵を知っていることを意味するものではありません。

2つのハンドシェイクメッセージがキーを確立し、最初のメッセージはデータを送信し、応答者に確認する 発信者 応答者 Initiation:148 B、一時公開鍵 + 暗号化アイデンティティ + 暗号化タイムスタンプ Response:92 B、一時公開鍵 + 空のプレーンテキスト認証タグ Transport:データまたは空のkeepalive、新しいセッションキーを確認 長さはWireGuardメッセージのみであり、UDP/IPは含まれません。Cookie返信は条件付きでトリガーされる別のパスです。

ハンドシェイクには複数の静的/一時DHおよびトランスクリプトの混合が含まれており、1つの一時DH結果に単純化して公開鍵ハッシュを自由に追加することはできません。正しいDHの等式はDH(a_private, b_public) = DH(b_private, a_public)です。public/publicやprivate/privateを記述することは、別の同等計算ではありません。

応答者が2番目のメッセージを送信した後、発信者が新しいキーを使用してTransportメッセージを送信して受信して初めて、その方向のキー確認が得られます。発信者にキューイングされたデータがない場合、空のパケットを送信してこのステップを完了できます。WireGuardプロトコル

ハンドシェイクメッセージの長さはどのようにして得られるのか

メッセージフィールド予算合計
Initiationタイプ/予約4 + sender4 + 一時公開鍵32 + 暗号化静的48 + 暗号化タイムスタンプ28 + mac1/mac2各16148 B
Responseタイプ/予約4 + sender4 + receiver4 + 一時公開鍵32 + 空のプレーンテキストタグ16 + mac1/mac2各1692 B
Cookie replyタイプ/予約4 + receiver4 + nonce24 + 暗号化cookie3264 B

encrypted_staticとencrypted_timestampはそれぞれ認証タグを運び、1つの「アイデンティティ+タイムスタンプ」の単一AEADフィールドに合成することはできません。タイプは1 Bのタイプと3 Bのゼロ予約ビットで構成され、カーネルはチェックしやすいようにリトルエンディアン32ビットで保存します。インデックスとTransportカウンターも規定どおりリトルエンディアンでエンコードされます。

データプレーン:IPパケット、パディング、カウンター、認証タグ

Transportメッセージヘッダーは16 Bです:タイプ/予約4、receiver index4、counter8。暗号化コンテンツの後に16 Bのタグがあるため、空のkeepaliveのWireGuardメッセージは32 Bです。receiver indexは対応するキー状態を特定し、ピアの公開鍵をプレーンテキストで運びません。

内部IPパケットは、微細な長さの漏洩を減らすためにゼロパディングされる場合があります。Linuxの送信パスでは通常16 Bにアライメントされますが、パディングは内部MTUなどの境界制約の影響も受けます。MTUの頂点で常に次の16倍数にパディングするわけではありません。以下は、GSOセグメンテーションなどの複雑なパスを含まない、単一の通常の内部パケットの計算です。

import struct

assert 4 + 4 + 32 + 48 + 28 + 16 + 16 == 148
assert 4 + 4 + 4 + 32 + 16 + 16 + 16 == 92
assert 4 + 4 + 24 + 32 == 64
header = struct.pack('<IIQ', 4, 0x12345678, 9)
assert len(header) == 16 and header.hex() == '04000000785634120900000000000000'

def outer_size(inner, mtu=1420, ip_header=40):
    assert 0 <= inner <= mtu
    padded = min(mtu, ((inner + 15) // 16) * 16)
    return ip_header + 8 + 16 + padded + 16

assert outer_size(0) == 80
assert outer_size(1000) == 1088  # 1008 Bにパディング、外部IPv6
assert outer_size(1420) == 1500 # パディングはMTUの制約を受ける
assert outer_size(1000, ip_header=20) == 1068
print('空パケット / 1000 B内部 / 1420 B内部のIPv6総長:', outer_size(0), outer_size(1000), outer_size(1420))

32 BはWireGuardメッセージの下限であり、外部IPv4 + UDP後では60 B、外部IPv6では80 Bです。リンク層は含まれません。1420は通常1500−40−8−32の予算から来ており、実際のパス、IPv6拡張ヘッダー、または他のカプセル化により利用可能な値が変化します。

counterはAEAD nonceの構築に参加し、各方向とキーの世代ごとに状態があります。受信側は一定の並べ替えを許容するためにウィンドウを使用し、重複を拒否します。TCPのように失われた内部データパケットを再送信することはありません。信頼性は内部プロトコルによって決定されます。WireGuardホワイトペーパー

順序不同は許容できるが、なぜ重複は拒否しなければならないのか?

7を先に配信し、次に3を配信する場合:後者は小さいがウィンドウ内にあるため、受け入れ可能です。3を再度配信するのが重複です。15を配信した後、ウィンドウの左端は8に移動し、その後に来る7はすでに古すぎます。すべての状態は、認証に成功したパケットでのみ進行します。

図を準備しています
順序不同は許容できるが、なぜ重複は拒否しなければならないのか?

カウンターが最大値より小さいからといって、必ずしもリプレイとは限りません。ウィンドウ内にあるかどうか、すでに受信したかどうかを確認する必要があります。古すぎるものや重複は拒否され、失われた位置は自動的に再送信をトリガーしません。

タイマー:イベントに基づいてキーを維持し、無条件で120秒ごとに再接続しない

以下は、Linux 7.2のdrivers/net/wireguard/messages.h、timers.c、send.c、およびreceive.cに基づいて確認します。ウェブサイト概要の簡略化された単位や古い公式は、具体的な実装の定数とトリガー条件を上書きすべきではありません。

条件または定数ローカル実装での意味
REKEY_AFTER_TIME=120 s発信者の送信パスはキーの年齢に基づいて更新などをトリガーする
REJECT_AFTER_TIME=180 s対応するキーの寿命を超えると使用を拒否する
REKEY_TIMEOUT=5 sハンドシェイクが応答しない場合の再試行の基本間隔。ジッターを追加
KEEPALIVE_TIMEOUT=10 sデータを受信した後、返信がない場合、空のパケットなどのイベント動作を手配する
REKEY_AFTER_MESSAGES=2^60メッセージ数に基づいて更新閾値をトリガーする。唯一の安全な制限ではない
REJECT_AFTER_TIME×3=540 s対応する沈黙条件下での残余キー材料のクリーンアップのタイマースケール

送信パス、受信パス、ハンドシェイク再試行は異なるトリガーポイントです。現在の受信パスの最終更新では、180−10−5=165秒という年齢閾値が使用され、発信者の役割などの条件が付随します。120−10−5をそのまま適用することはできず、120秒間にトラフィックがない場合に必ずハンドシェイクをトリガーすると解釈することもできません。

ハンドシェイク再試行は5秒にランダムジッターを加えて進行し、試行回数に上限があります。ここでは5、10、20秒の指数関数的バックオフではありません。デフォルトでPersistentKeepaliveが無効の場合、アイドル状態のデバイスは静かにしていることができますが、以前のトラフィックによってトリガーされたプロトコルの空パケットや再試行が存在する可能性があります。

新旧のキーは遷移期間中に共存でき、パケット損失や並べ替えに適応します。切り替えは、古い状態をすべて即座に消去することを意味しません。固定メッセージ閾値を「何万年後にキーを交換する」という使用保証に変換することはできません。時間閾値や他の制限がより早く処理に参加するためです。

Cookie、ローミング、NATの境界

mac1は、高価な処理の前にサーバーがメッセージをフィルタリングするのに役立ちますが、公開された応答者のアイデンティティから派生した関連材料は、許可されたピアがすでに認証されたことを意味するものではありません。負荷条件が満たされ、有効なcookieがない場合、cookieチャレンジを返して、後続のmac2で対応する証明を提供させることができます。すべての初回ハンドシェイクで必ずcookieがトリガーされるわけではありません。

mac2はcookieを使用して計算されたMACであり、cookieの元の値をmac2フィールドに直接挿入するものではありません。cookieはソースの到達可能性を検証し、リソースの乱用を減らすのに役立ちますが、完全なピアアイデンティティ認証に代わるものではありません。応答メッセージのmac2もプロトコルの条件に従って処理する必要があり、常にゼロとしてマークされるわけではありません。

有効な認証トラフィックは、ピアの外部エンドポイントを更新し、アドレス変更後のローミングをサポートします。ただし、双方に到達可能なエンドポイントがなく、マッピングが確立できない場合、WireGuard自体には自動協調発見や一般的なリレーサービスがありません。

PersistentKeepaliveは、アイドル状態のNATまたは状態ファイアウォールマッピングを有効に保つために使用され、デフォルトでは無効です。25秒は一般的な適用例であり、プロトコルの強制値ではありません。利用可能なパスを維持しますが、すべてのNAT組み合わせが相互に通信できることを保証するものではなく、すでに到達不可能なネットワークを無限に維持するものでもありません。WireGuardクイックスタート

ルート出口と診断

フルトンネルルーティングの場合、外部UDPがピアエンドポイントへのトラフィックに正常な出口があることを確保し、同じWireGuardインターフェースに再度送られないようにする必要があります。ポリシーマークとネットワーク名前空間は、内部ルーティングと外部ソケットが属するネットワーク環境を分離するのに役立ちます。WireGuardルーティングと名前空間

以下の選択的なコマンドは、既存のインターフェースを観察するために使用され、秘密鍵やPSKは出力せず、設定も変更しません。

wg show all endpoints
wg show all allowed-ips
wg show all latest-handshakes
wg show all transfer
ip route show
ip -6 route show
現象区別される問題
最近のハンドシェイクがないトラフィックがトリガーされたか、エンドポイントが到達可能か、キーが一致するか
ハンドシェイクは成功したが業務が失敗システムルーティング、AllowedIPs送信元検証、フォワーディング、ファイアウォール
小さなパケットは通るが、大きなパケットは固まる内部MTU、外部パス、ICMPフィードバック
アイドル後にパッシブな着信が失敗NATマッピングの寿命とkeepaliveの必要性
ローミング後に片方向通信外部エンドポイントの更新、帰還パス、フィルタリング

latest-handshakesは時間記録であり、アプリケーションのヘルスプローブではありません。長期間アイドル状態の場合、古い時間が自動的に故障を意味するわけではありません。transferカウントの増加も、方向と実際の内部アプリケーションの結果に合わせて解釈する必要があります。

継続して参照