はじめに
以前に社内勉強会で SSH について学ぶ機会があったのですが、当時は流れをつかみきれず、置いていかれた感がありました。今回はそのリベンジとして、自分で手を動かして観察・整理してみた記録です。
題材は GitHub への接続を例にします。
ただし、暗号化アルゴリズムの数学的な仕組みや細かい仕様には踏み込まず、あくまで 「通信が始まるまでの流れ」と「各段階で何が行われているか」 に焦点を当てています。
※ 本記事は学習記録としてまとめたもので、RFC や実装をベースにしつつも一部は簡略化しています。
誤りや補足などあれば、ぜひコメント等で教えていただけると嬉しいです。
SSH が実際のデータ通信を開始するまでの流れ
RFC4253 から、ざっくりこんな流れだと理解しました。
※ 記述を簡略化するため、クライアントを「私」、サーバーを「GitHub」とします。
-
接続開始(まだ平文)
私と GitHub が、使用する SSH バージョンを交換
-
暗号スイートのネゴシエーション
私と GitHub が、暗号化に使えるアルゴリズムを交換し、共通の方式を選択クライアントとサーバーが、鍵交換方式・ホスト鍵種別・暗号・MAC・圧縮などの候補を一覧で送り合い、共通の方式を選びます。
-
鍵交換(Key Exchange)
Diffie–Hellman / ECDH (Elliptic-curve Diffie-Hellman) の結果と、バージョン情報やアルゴリズム交渉の内容などを混ぜ込み、セッションの間だけ有効な共有鍵を生成
-
GitHub のホスト鍵検証
GitHub「自分が本物の GitHub という証拠を見せます」GitHub は自分の秘密鍵で署名を作成 → 私は保存済みの GitHub の公開ホスト鍵で検証
この検証に使う公開ホスト鍵は、初回接続時の TOFU で記録されたもの、もしくは事前に配布・検証されたものを前提にします(TOFU については詳細後述)
-
暗号化開始
作成した共有鍵で暗号化通信を開始
-
ユーザーの本人確認
私「自分が誰であるかの証明として、署名を送ります」
私が秘密鍵で署名を作成 → GitHub は、登録済みの私の公開鍵で検証
補足:SSHの「本物確認」は TOFU 方式
HTTPS(TLS) は、認証局(CA)が「この公開鍵は確かに github.com のもの」と証明してくれるので、ブラウザは初回から本物と判断できます。
一方 SSH は、CA のような第三者認証は基本的に使わず、TOFU(Trust On First Use) モデルを採用しています。
- 初回接続時、サーバーの公開鍵を受け取る
- ユーザーは「この鍵、保存していい?」と聞かれる(
known_hosts
に登録) - 次回以降は、サーバーが提示した鍵と保存済みの鍵を照合し、一致すれば安全とみなす
The authenticity of host '123.456.XXX.XX (123.456.XXX.XX)' can't be established.
RSA key fingerprint is SHA256:uNiVztksCsDhcc0u9e8XXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)?
はじめて SSH 接続するときにこんな文言が出ると思いますが、これのことです(いつも私は めくらばんで登録してました…)
※ 実務では初回接続の前に、公式ドキュメントや社内資料など 別経路でホスト鍵フィンガープリントを確認してから known_hosts に登録するのが安全です。
Wireshark で実際に上記の流れを見てみよう
上の 6 ステップは、RFC に即した簡略化で、実際の処理をわかりやすく段階分けしたものです。
次は、実際に Wireshark でパケットを覗きながら、この流れが本当にこうなっているのかを確認してみます。
ちょっと細かい字がびっしりですが、下のスクショは、GitHub への SSH 接続時に流れたパケットをキャプチャしたものです。
「何が行われているのか」を追いやすいよう、下に対応するステップをまとめました。
No. | パケット内容 | SSHの流れ | 手順番号 |
---|---|---|---|
1136-1138 | TCP SYN / SYN-ACK / ACK | TCP接続の確立 | -(準備段階) |
1139-1140 | Client / Server: Protocol | SSHバージョン交換 | 1 |
1143-1145 | Client: Key Exchange Init | アルゴリズムのネゴシエーション | 2 |
1147 | Client: ECDH Key Exchange Init | 鍵交換(共有鍵生成 開始) | 3 |
1150 | Server: ECDH Key Exchange Reply | 鍵交換(共有鍵生成 完了)+ホスト認証 | 3 & 4 |
1151 | Server: New Keys | 暗号化開始準備(サーバ→クライアント) | 5(開始直前) |
1155 | Client: New Keys | 暗号化開始準備(クライアント→サーバ) | 5(開始直前) |
1157 | Client: Encrypted packet | 暗号化通信開始(ユーザー認証サービス要求など) | 5(開始直後) |
1160 以降 | Encrypted packet | ユーザー認証(公開鍵認証などが暗号化通信の中で行われる) | 6 |
【詳細】暗号アルゴリズムの候補リスト(KEXINIT)(クリックで展開)
クライアント → サーバー
サーバー → クライアント
両方を突き合わせた結果、このセッションでは
- 鍵交換方式:
curve25519-sha256
- ホスト鍵アルゴリズム:
ssh-ed25519
が選ばれました。
【詳細】鍵交換メッセージ(ECDH)の実体(クリックで展開)
クライアント → サーバー (SSH_MSG_KEX_ECDH_INIT
)
サーバー → クライアント (SSH_MSG_KEX_ECDH_REPLY
)
さて、ここで疑問が残ります。手順 4 が見えない。
暗号化開始前なら「署名っぽい」文言が見られるはずでは?
Wiresharkの一覧を眺めても、手順3(ECDHの鍵交換)まではしっかり見えるのに、「ホスト認証」が単独のパケットとして現れません。
実はこれ、鍵交換の「返事」パケット(SSH_MSG_KEX_ECDH_REPLY
)の中に同居しています。
No.1150 の Server: Elliptic Curve Diffie-Hellman Key Exchange Reply パケットの詳細を開いていくと、以下のように署名を確認できます。
表面的には「鍵交換の返事」ですが、その裏で「私は本物」という証明も済ませているわけです。
そのため Wireshark のプロトコル列には 手順 4 が単独で現れたようには見えません。
見えている部分を整理すると:
-
KEX host key
type: ssh-ed25519
- サーバー(GitHub)が自分の公開ホスト鍵を提示
-
ECDH server's ephemeral public key (Q_S)
- 一時的に生成されたECDH公開値(共有鍵生成用の材料の1つ)
-
KEX host signature
type: ssh-ed25519
-
Host signature data: 00000040...
の部分が、ハッシュHをホスト鍵で署名したバイナリ - クライアントはこれを検証して「本物のGitHubだ」と確認
RFC 4253 §8 "Diffie-Hellman Key Exchange" にも、
The exchange also serves to authenticate the server to the client using the server's public host key.(この鍵交換は、サーバーの公開ホスト鍵を使ってクライアントにサーバーを認証する役割も兼ねている)
と明記されています。
補足: 手順 6(ユーザー認証)は、手順 5 で暗号化が開始された直後に行われます。
そのため Wireshark では、No.1160 以降の「Encrypted packet」の中に含まれており、中身は確認できません。
見た目上は同じ暗号化パケットでも、その中でユーザーの署名送信やサーバー側での検証が行われています。
なお、今回は GitHub の公開鍵認証を使っていますが、SSH ではパスワード認証やキーボードインタラクティブ認証など、他の方式も利用できます。
他のサービスだと何が違う?
今回の例は GitHub での SSH を見てきましたが、例えば AWS EC2 でも基本の流れは同じです。
違うのは、SSH サーバーの準備をどこまでサービスがやってくれるかという点です。
-
EC2
- インスタンス作成時に登録した公開鍵を、自動でサーバー内の
~/.ssh/authorized_keys
に配置 - SSHサーバー (
sshd
) も最初から有効化済み - 初期ユーザー(
ec2-user
やubuntu
)も作成済み
→ ユーザーは「キーペア登録」と「セキュリティグループでポート22 開放」だけで接続可能
→ ただしファイアウォール設定や不要ポートの閉鎖、IAMポリシーなど本番運用上のセキュリティ設定は別途必要
- インスタンス作成時に登録した公開鍵を、自動でサーバー内の
-
自前で立てたサーバー
- OS に SSH サーバーをインストール & 起動設定
- キーペアの作成と、公開鍵のサーバーへの配置
- ファイアウォールやポート開放の設定
→ 接続に必要な環境を一から自分で整える必要あり
つまり、GitHub や EC2 は「最低限疎通できる状態」までは整えてくれます。ただし、安全に使うための最終的なセキュリティ対策は利用者の責任です。
おまけ: 未来の SSH は QUIC に乗るかも?
現在の SSH(v2)は TCP ベースですが、次世代の SSH3(草案) では、UDP ベースの HTTP/3(QUIC + TLS 1.3) を使う提案があります。
これにより、
- 接続確立が短縮(QUIC の 0-RTT や高速ハンドシェイク)
- モバイルや回線切り替え時もセッション維持
- OAuth2 など Web 由来の認証方式も利用可能
などの利点があります。
※「SSHをQUIC/TLS1.3上で動かす」提案はまだ標準化・普及が進行中です(“SSH3” という呼び名も非公式)。
QUIC の基本については、よろしければ以前に書いた記事をどうぞ(初心者向け・ざっくり解説です)
おわりに
今回は「SSH が通信を開始するまでの流れ」を、Wireshark で実際のパケットを追いながら確認しました。
普段は「認証しました」の一言で済まされる部分も、段階を分けて見ると意外と手順が多いことが分かります。
GitHub には SSH だけでなく HTTPS もあります。次は HTTPS(TLS)の接続を同じように追い、SSH との違いを観察してみたいと思います。