SSH PROTOCOL  ·  ■ クライアント   ■ サーバー

知らない相手と、
盗聴される回線の上で、
安全にログインする

SSH認証が複雑に見えるのは、目的の違う3つのフェーズが順番に並んでいるから。分けて見れば怖くない。カギは「クライアントとサーバーが、逆向きに確かめ合う」という一点です。

0 全体像

3つのフェーズを、順番に通り抜ける

SSHは接続してからログインが確定するまで、次の3段階を必ずこの順で通ります。①と③で「疑う向き」が逆なのが最大のポイント。①ではクライアントがサーバーを疑い、③ではサーバーがあなたを疑う。お互いを確かめ合うわけです。

Phase 01

サーバー認証

つなぐ相手は本物のサーバー? 偽物にすり替えられていない?

確認する側 → クライアント
Phase 02

鍵共有

誰にも読めない暗号トンネルを先に作る。まだ本人確認はしない。

両者で共通鍵を作る
Phase 03

ユーザー認証

ログインしていいのはあなた? 許可された人か?

確認する側 → サーバー

順番も意味があります。②で暗号トンネルを先に作るから、③のパスワードや署名が平文で流れません。「まだ認証は終わっていない」と本が念押しするのはこの理由です。

最重要の勘所

2つのファイルを取り違えないこと

初学者が必ずつまずくのがこの2ファイル。名前も置き場所も役割も“逆”です。ここに色をつけて覚えると、後の図が全部つながります。

known_hosts
クライアント側にある
中身
知っているサーバーの公開鍵リスト
役割
サーバーが本物か確認(①)
疑う向き
クライアント → サーバー
authorized_keys
サーバー側にある
中身
ログインを許すユーザーの公開鍵リスト
役割
ユーザーが本物か確認(③)
疑う向き
サーバー → ユーザー

この先、known_hosts は必ずクライアント色authorized_keys は必ずサーバー色で出てきます。色で役割を思い出せるようにしてあります。

1 サーバー認証フェーズ

「この接続先、本物?」をクライアントが確かめる

回線の途中で偽サーバーにすり替えられていないかを確認します。判断材料は、クライアント手元の known_hosts です。

SSHクライアントSSHサーバー
クライアント ▶ サーバー接続要求を送る
サーバー ▶ クライアントホスト認証用の公開鍵 pk_s を返す
照合 // known_hosts前に保存した公開鍵と一致する?
一致した → 前と同じ本物のサーバー → 認証OK
記録が無い / 食い違う → フィンガープリントを表示して警告

「記録が無い」ときに出るのが、初めての接続で必ず見るこの警告です。公開鍵そのものは長いので、ハッシュ(フィンガープリント)だけを見せて「この相手、信じる?」と聞いてきます。

bash — ssh
$ ssh mowree@192.168.56.109
The authenticity of host '192.168.56.109' can't be established.
ED25519 key fingerprint is SHA256:9x8s...Kf2a.
Are you sure you want to continue connecting (yes/no)? yes
# yes と答えると、この公開鍵が known_hosts に保存される
# → 次回からは一致するので、黙って繋がる
⚠ すり替えの検知

一度保存した鍵と違う鍵が飛んできたら「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」という強い警告が出ます。これが中間者攻撃(なりすまし)に気づくための仕組みです。安易に鍵を消して繋ぎ直すのは危険。

2 鍵共有フェーズ

誰にも読めない暗号トンネルを、先に作る

サーバーが本物だと分かったので、次は盗聴対策。回線は誰でも覗ける前提なので、クライアントとサーバーだけが知る共通鍵を、盗聴されても漏れない方法で共有します。

これに使うのが Diffie–Hellman鍵共有。「やりとりを全部見ていても、共通鍵そのものは計算できない」という数学の性質を使います。ここで作るセッションID(この接続だけの使い捨ての値)が、あとで③でも登場するので覚えておいてください。

ここが誤解ポイント

この時点でも、まだ「あなたが誰か」は一切確認していません。できたのは暗号トンネルだけ。本人確認は次の③でようやく始まります。

3 ユーザー認証フェーズ

「あなた、ログインしていい人?」をサーバーが確かめる

パスワード認証もありますが、SSHの主役は公開鍵認証。使う道具はデジタル署名です。まず前提から。

前提:鍵ペアを作り、公開鍵だけ預けておく

ssh-keygen で自分のPCに鍵ペアを作ります。

sk(署名鍵 / 秘密鍵)
~/.ssh/id_ed25519 ・ 自分だけが持つ
使い道
署名を作る。絶対に渡さない
vk(検証鍵 / 公開鍵)
~/.ssh/id_ed25519.pub ・ 配ってよい
使い道
署名を検証する。サーバーに預ける

この公開鍵 vk を、ログインしたいサーバーの authorized_keys に事前登録しておきます。「この鍵の持ち主は入れてよい」という名簿に一行加えるイメージです。

本番:署名を作って送り、サーバーが検証する

クライアントが署名を作る

「ユーザー名・自分の公開鍵・セッションID」をまとめたデータに、秘密鍵 sk で署名 σ を作る(Sign)。

データ+署名を送る

元データと署名 σ をセットにしてサーバーへ送信する。

名簿と照合する

送られた公開鍵が authorized_keys に載っているか確認。載っていなければ即NG。

署名を検証する

その公開鍵 vk で署名 σ を検証(Ver)。出力が 1(正しい) なら認証OK、ログイン許可。

? なぜ公開鍵がバレてもなりすませないのか

サーバーが見ているのは「公開鍵を知っているか」ではなく「その公開鍵に対応する秘密鍵で署名を作れるか」です。署名は秘密鍵がないと作れない。攻撃者は公開鍵を持っていても秘密鍵を持っていないので、正しい署名を作れず Ver が 0 を返して弾かれます。ここが公開鍵暗号の非対称性の肝です。

? なぜ署名対象にセッションIDを混ぜるのか

もし毎回同じデータに署名していたら、攻撃者が過去に流れた署名を盗んで使い回す(リプレイ攻撃)ことができてしまいます。②で作った使い捨てのセッションIDを署名対象に含めるので、盗んだ署名は次の接続では通用しません。
※本の図の「データはセッションIDを含んでいません」は“送信するデータ本体”の話。“署名対象”には入っている、という区別です。

+ よくある混同

パスフレーズは、サーバーに送る秘密ではない

「秘密鍵のパスフレーズ」と「サーバーのパスワード認証」は別物です。パスフレーズは、手元の秘密鍵ファイルを復号するためのもの。ネットワークには流れません。

パスフレーズ無しで鍵を作ると、その鍵ファイルはそのまま使えてしまいます(=ファイルを盗まれた瞬間アウト)。だからパスフレーズは付けておくのが安全です。

攻撃者の視点

authorized_keys に一行書ければ、堂々と入れる

authorized_keys は「この公開鍵の持ち主はログインを許す」という名簿でした。ということは——

⚔ 権限奪取 / 永続化

攻撃者が何らかの手段でサーバーの ~/.ssh/authorized_keys自分の公開鍵を書き込めれば、自分の秘密鍵で正規ユーザーとしてSSHログインできてしまう。前に見た LFI や書き込み可能な脆弱性から authorized_keys に一行追記する——これはCTFでも実戦でも定番の手口です。

だから防御側は、authorized_keys書き込み権限見覚えのない公開鍵エントリを監視します。侵害ホストを調べるとき、.ssh/authorized_keys に知らない鍵が増えていないかは、.bashrc の改ざん確認と並ぶ定番チェックポイントです。

30秒でおさらい

全体を一本の線でつなぐ

  • ①サーバー認証 — クライアントが known_hosts でサーバーを確認。初回の「本当に繋ぐ?」がこれ。
  • ②鍵共有 — Diffie–Hellmanで暗号トンネルを先に作る。まだ本人確認はしない。
  • ③ユーザー認証 — サーバーが authorized_keys + 署名検証であなたを確認。秘密鍵を持つ本人しか署名を作れない。
  • 向きが逆known_hosts=クライアントがサーバーを疑う / authorized_keys=サーバーがユーザーを疑う。ここが腹落ちすれば図が全部つながる。
  • 攻撃面authorized_keys に公開鍵を書ければ侵入できる。書き込み経路(LFIなど)と鍵の監視が要。