SSHの仕組み

SSHが内部的にどういう仕組みなのか知らず使っていたので調べてみました!

どこか間違っていたらブログ下のメールよりお願いします🙇‍♂️

全体の流れ

SSHはおおまかに、次のような流れで通信しています。

  1. Diffie-Hellman鍵交換を用いてクライアントとサーバーしか知らない共通鍵Kを生成する。このとき同時にクライアントは正しいサーバーと通信していることも検証。

  2. 共通鍵Kを用いて暗号化された通信経路Pを作る。

  3. Pの上で、パスワードや公開鍵などの方法でサーバー側がクライアントを認証する。

  4. Pの上で、shellやport-forwardなど好きなことを行う。

1. 鍵交換&サーバーの検証

Diffie-Hellman鍵交換とは、以下のようなお互いに共通鍵を得るプロトコルです。 アリスとボブが鍵交換するとき、通信経路上で盗聴される可能性があっても、最終的にアリスとボブのみ共通の鍵を知った状態になります。

まず値の大きな素数$p$を用意する。また$g$を${\displaystyle ({\mathbb {Z} }/p{\mathbb {Z} })^{\ast }}$の生成元とする。この$g$と$p$は公開されているものとする。

いまアリスとボブが通信を行うとする。このときアリスとボブはお互い自分だけの知る秘密の値 $a, b$ を選択する、この値は 0 以上 $p−2$ 以下の中からランダムに選ぶ。(ここで、ゼロや小さな値($g^a < p$ となる $a$ 等)を選択すると安全性が損なわれるが、そのような確率は無視できるほど小さい。) アリスは以下の値 $A$ を計算してそれをボブに送信する。

$A=g^{a} \ {\bmod {p}}$

ボブも同様に以下の値 B を計算してそれをアリスに送信する。

$ B=g^{b} \ {\bmod {p}}$

アリスは自分だけの知る秘密の値 a とボブから送られてきて受信した値 B から以下の値を計算する。

$ K_{A}=B^{a} \ {\bmod {p}}$

ボブも自分だけの知る秘密の値 b とアリスから送られてきて受信した値 A から以下の値を計算する。

$ K_{B}=A^{b} \ {\bmod {p}}$

このときアリスとボブが計算した $K_{A}$ と $K_{B}$ は

$ K_{A}=K_{B}=g^{ab} \ {\bmod {p}}$

となっていて一致するので、以後この値を共通鍵暗号方式の鍵$K$として使用する。

wikipedia / ディフィー・ヘルマン鍵共有より引用

Diffie-Hellman鍵交換はアリスとボブの通信経路上で鍵が盗聴されることはないですが、これは「アリスとボブが」通信していた場合に限ります。 というのは、第三者Cさんがどちらかになりすますことで中間者攻撃が可能です。

具体的には、アリスとボブが鍵交換して暗号化された通信経路を作ろうとするときに、Cさんがアリスに対してはボブになりすまし、ボブに対してはアリスになりすますことで、A <-> C、C <-> Bの暗号化された通信経路を作ります。 アリスとボブはお互いにボブ、アリスと暗号化された(と思っている)経路で通信しますが、実際にはCさんが間に挟まって復号/暗号化しながら通信をリレーしているため、盗聴することができてしまいます。 詳しくはwikipedia / ディフィー・ヘルマン鍵共有#中間者攻撃を参照してください。

このため、SSHではDiffie-Hellman鍵交換を行いながら同時に、サーバーの公開鍵/秘密鍵を用いてクライアントが正しいサーバーと通信しているか検証します。

クライアントがアリス、サーバーがボブだとすると、サーバーはクライアントに対して$B (= g^{b} \ {\bmod {p}})$を送信します。

このときに、サーバーがサーバーの秘密鍵で「サーバーの公開鍵と$B$」に電子署名して送信することで、クライアントはサーバーの公開鍵を用いて署名を検証し、送ってきた相手が期待したサーバーと確信できます。

またそのようにして得られた$K_{A} = (K_{B})$を用いて暗号化された通信経路で通信できれば、サーバーに対しても正しく$A$が送信できていることがわかり、中間者攻撃の問題は解決できます。

サーバーから送られてきた「サーバーの公開鍵」が正しいことも検証する必要がありますが、それは~/.ssh/known_hostsとマッチして確かめます。 マッチしないとき(大抵ssh初回アクセス時)は、

$ ssh 192.168.100.1
...
The authenticity of host '192.168.100.1 (192.168.100.1)' can't be established.
RSA key fingerprint is h1:g2:3o:4e:h5:67:ho:ge:12:3f:5u:g8:a0.
Are you sure you want to continue connecting (yes/no)?

みたいに聞かれて、yesと答えると~/.ssh/known_hostsに追加されて接続されます(のでよくわからないけどyesはヤバい)。

ちなみに実際にはDiffie-Hellman鍵交換ではない鍵交換、楕円曲線ディフィー・ヘルマン鍵共有が使われていることもあります。

共通鍵を用いて暗号化された通信経路を作成

一回共通鍵を得てしまえばあとはこっちのもので、AESとかChacha20とか色々暗号化方式があるみたいです。

CBC系の暗号化方式はSSHプロトコルの仕様上に問題があるため、基本的に使用せず、Chacha20やAES-GCM、AES-CTRを利用するようにします。

暗号化された通信経路上で、サーバー側がクライアントを認証

まあ普通にベーシック認証 over HTTPSみたいにユーザー名とパスワード送信して認証とかしたりします。

あとはよくあるのは、サーバー側の~/.ssh/authorized_keysにクライアントの公開鍵を置いて、クライアントの秘密鍵で認証する方式です。 妥当な説明 - SSHの公開鍵認証における良くある誤解の話によれば、Diffie-Hellman鍵交換で得た共通鍵から作られたセッションIDをクライアントの秘密鍵で電子署名して渡すことで、サーバーが~/.ssh/authorized_keysで検証して認証するようです。