SSHの仕組み
SSHを使ったことはあっても、「なぜSSHは安全なのか?」「公開鍵・秘密鍵・共通鍵は、それぞれ何に使われているのか?」「ssh -T git@github.com を実行したとき、裏では何が起きているのか?」
まで説明しようとすると、意外と難しいのではないでしょうか。
私自身、SSHについて「暗号化された安全な通信をする仕組み」ということは知っていましたが、鍵交換、サーバー認証、ユーザー認証の関係をきちんと理解できていませんでした。
SSHを考えるときに、解決しようとしている問題を3つに分けると理解しやすくなります。
- 安全な通信路を作る
- 接続先が本物のサーバーか確認する
- 接続してきたユーザーが正しいユーザーか確認する
この観点を念頭に、SSHについて整理していきます。
SSH接続では何が起きているのか
たとえば、次のようにSSHでサーバーへ接続するとします。
ssh user@example.com
このコマンドは、SSHを使ってexample.comというサーバーへ、userというユーザー名で接続する、という意味です。
コマンドを実行すると、大まかに以下の3つのことが行われます。
- 安全な通信路を作る
- 接続先が本物のサーバーか確認する
- 接続してきたユーザーが正しいユーザーか確認する
それぞれのステップで何が起こっているのか、順番に整理していきます。
1. 安全な通信路を作る
まず必要なのは、通信内容を第三者に盗み見られないようにすることです。 大量のデータを効率よく暗号化するには、一般的に共通鍵暗号が使われます。 共通鍵暗号では、クライアント(自分)とサーバー(接続先)が同じ秘密の鍵を持ち、それぞれ暗号化・復号を行います。
クライアント サーバー
共有している秘密の鍵 共有している秘密の鍵
↓ ↓
平文 ── 暗号化 ──> 暗号文 ── 復号 ──> 平文
このようにクライアントとサーバーしか知らない共通鍵を用いることで、第三者に盗聴されることなく送受信を行うことができます。
共通鍵をどうやって相手に渡すのか?
しかし、ここで問題が生じます。初めて通信を行う際に、誰にもばれないように共通鍵を共有する必要があるということです。
「これから使う秘密の鍵は abc123 です」
とネットワーク上で送ったらどうなるでしょうか。 盗聴している第三者にも、
abc123
が見えてしまいます。 すると、その後いくら通信を暗号化しても、盗聴者が同じ鍵を持っているため意味がありません。 つまり、
暗号化通信を始めるには共通鍵が必要なのに、その共通鍵を安全に渡すための暗号化通信がまだ存在しない
という問題があります。 この問題を解決するのが鍵交換です。
Diffie-Hellman
鍵交換方式の代表例がDiffie-Hellman鍵交換です。 ポイントは、最終的な共有秘密そのものをネットワーク上で送らないことです。 クライアントとサーバーは公開情報を交換し、それぞれが自分だけの秘密情報を使って計算することで、最終的に同じ共有秘密を作ります。
仕組みを理解するため、非常に小さな数字を使って考えてみます。まず、誰から見られてもよい値として、
p = 23
g = 5
を使います。 この2つは公開情報ですので、第三者に盗聴されても問題ありません。 次にAとBが、それぞれ秘密の数字を決めます。
Aの秘密値 a = 6
Bの秘密値 b = 15
この a と b はネットワークへ送信しないため、相手は値を知りません。
次に、Aは以下の計算をします。
g^a mod p = 5^6 mod 23 = 8
同様に、Bも以下の計算を行います。
g^b mod p = 5^15 mod 23 = 19
ちなみに、x mod yは、xをyで割ったときの余りを表します。
お互いの計算結果(公開値)である 8 と 19 を、それぞれ相手に送信します。
そして、Aは、Bから受け取った 19 と自分だけが知っている秘密の値である 6 を使って、以下の計算を行います。
19^6 mod 23 = 2
同様に、Bは、Aから受け取った 8 と自分だけが知っている秘密の値である 15 を使って以下の計算を行います。
8^15 mod 23 = 2
結果はどちらも、2になります。詳細は省きますが、この計算はaとbにどのような値を設定しても一致します。この2が通信を暗号化するために使用する共通鍵のもととなります。
盗聴者には何が見えるのか
仮に盗聴されたとして、盗聴者には、
p = 23
g = 5
Aの公開値 = 8
Bの公開値 = 19
が見えています。しかし、お互いに設定した秘密の値である
a = 6
b = 15
はネットワーク上を流れていません。 実際の暗号で十分に大きな値を使用した場合、公開値から元の秘密値を現実的な時間で求めることは困難です。 そのため、共有秘密そのものを一度も送信せず、両者が同じ秘密を得ることができます。
なお、ここで使った p = 23 などの値は仕組みを理解するための極端に小さな例です。また、厳密には計算した値をそのまま使って暗号化するわけではありません。あくまで単純化した例であることには留意してください。
2. 接続先が本物のサーバーか確認する
ここまでで、盗聴者には知られない共有秘密を作り、通信を暗号化することができました。 しかし、まだ重要な問題が残っています。
今、安全に通信している相手は、本当に自分が接続したかったサーバーなのか?
という問題です。たとえば、攻撃者がクライアントと本物のサーバーの間に入り込んだとします。
クライアント 攻撃者 本物のサーバー
|<--- 鍵交換 --->| |
| |<--- 鍵交換 ---->|
攻撃者がクライアントとは別の共有秘密を作り、本物のサーバーとも別の共有秘密を作れるのであれば、単にDiffie-Hellmanを実行するだけでは、 「安全に話せる相手はできたが、その相手が誰なのか分からない」 という状態になります。
そこで必要になるのが、サーバー認証です。
ホスト鍵でサーバーを認証する
SSHサーバーは、ホスト鍵を持っています。ホスト鍵には、
ホスト秘密鍵
ホスト公開鍵
があります。 秘密鍵はサーバーが保持し、誰にも公開しません。 一方で、公開鍵はクライアントに提示しても問題のない情報です。
公開鍵と秘密鍵はペアになっており、代表的には次の二つの目的で使われます。
1. 暗号化・復号
公開鍵で暗号化し、秘密鍵で復号する暗号化方式です。公開鍵で暗号化した情報は、ペアである秘密鍵でしか復号できません。
これを用いて、例えばとある企業に個人情報を登録するときに、企業が公開している公開鍵を用いて個人情報を暗号化し送付します。すると、この暗号化された個人情報は、盗聴されても秘密鍵がないと復号することができません。秘密鍵が流出しない限り、送付先の企業以外に個人情報が知られることはありません。
2. 電子署名
もう一方が、秘密鍵で署名し、公開鍵で検証する電子署名です。電子署名の目的は情報の秘匿ではなく、送信元の正当性を示すことです。秘密鍵で行った署名は、先ほどと同様にペアである公開鍵でしか検証できません。
例えば、ある企業の公開鍵が正しいものだと事前に確認できている場合(公式HPで公開されている等)、その公開鍵で企業から送られてきた署名を検証できれば、秘密鍵を所有している企業が署名して送付したことが確認できます。つまり、送信者が公開鍵を公開している主体であることが保証されます。
また、署名付きのデータが改ざんされていれば、公開鍵による検証に失敗します。そのため、送信元の確認だけでなく、データの改ざんがされていないことも確認できます。
SSHのサーバー認証で利用するのは、この電子署名の仕組みです。鍵交換で使用した情報に対してサーバーは秘密鍵で署名し、クライアントに送付します。クライアントはサーバーの公開鍵で署名を検証することで、相手が接続したいサーバーであることを確認することができます。
ただし、クライアントは公開鍵が接続したいサーバーのものであることは事前に確認する必要があります。フィンガープリントの確認等、人間の目で行うことが一般的です。
2回目以降はknown_hostsと比較できる
一度信頼したホスト公開鍵は、一般的なOpenSSH環境ではknown_hostsに保存されます。2回目以降の接続では、提示された公開鍵がknown_hostsに保存されているものと一致しているかを確認します。さらに、サーバーが秘密鍵を用いて実施した署名を公開鍵で検証することで、初回接続時に信頼したサーバーと同じ主体に接続できていることを確認します。
公開鍵そのものは秘密情報ではないため、攻撃者が本物のサーバーの公開鍵をコピーして提示することが可能です。そのため、2回目以降の接続でも、秘密鍵で施した署名を公開鍵で検証できるかどうかを確認する必要があります。
3. 接続してきたユーザーが正しいユーザーか確認する
ここまでで、通信の暗号化と接続先のサーバーが正しいことが確認できました。 しかし、サーバーが正しい相手でも、誰でも自由にログインできたら困ります。
そこでSSHでは、安全な通信路を確立したうえでユーザー認証を行います。 代表的なユーザー認証には、
- パスワード認証
- 公開鍵認証
などがあります。 ここでは、GitHubなどでもよく利用される公開鍵認証を見てみます。
SSHの公開鍵認証でも電子署名を行う
ユーザー認証用にも、サーバー認証と同様に電子署名を使用します。ユーザー側も以下の二つの鍵を保有しています。
ユーザー秘密鍵
ユーザー公開鍵
まず、事前準備として公開鍵をサーバー側へ登録します。
一方、秘密鍵はユーザーのPCに保持します。 重要なのは、秘密鍵は誰にも公開しないことです。秘密鍵はユーザーPCのみに保存し、絶対に流出しないように注意します。
ログイン時には、クライアントが秘密鍵を使ってSSHセッションに紐づいた認証データへ署名します。 そして、サーバーはあらかじめ登録されている公開鍵を使って署名を検証します。
これによってサーバーは、
この接続者は、登録済み公開鍵に対応する秘密鍵を持っている
と判断できます。
まとめ
SSH接続を、3つのステップに分けて整理してきました。
安全な通信路を作る
↓
鍵交換によって共有秘密を合意し、
セッション用の鍵を作る
サーバーが本物か確認する
↓
ホスト秘密鍵による署名と
ホスト公開鍵による検証を行う
ユーザーが本人か確認する
↓
ユーザー秘密鍵による署名と
登録済みユーザー公開鍵による検証を行う
特に重要なのは、「通信を暗号化できること」と「通信相手が本物であること」は別問題 だという点です。
そして、「サーバーが本物であること」と「接続しているユーザーが本人であること」も別問題 です。
SSHは単に「暗号化された接続」なのではなく、安全な通信路の確立・接続先の確認・接続者の確認を組み合わせることで、安全なリモート接続を実現している仕組みなのです。
参考資料
- RFC 4253: The Secure Shell (SSH) Transport Layer Protocol
- RFC 4252: The Secure Shell (SSH) Authentication Protocol