SSH のしくみや手順を理解したい！

# まえがき

SSH の仕組みについて理解したいわたしです。SSH って公開鍵認証とパスワード認証があって、通信は共通鍵で秘匿化して～みたいな感じのあっさりとした理解が現状ですが、実際に SSH を叩いたり、公開鍵の中身をみたりしてみると、rs とか ed25519 とかよくわからないものがいっぱい。ということで、いろいろ整理して自分なりに理解しよう！というのが目的です。「理解した！」じゃなくて「理解したい！」なので注意。

# SSH とは

SSH とは、Secure Shell（セキュアシェル）の略で、ざっくりいうと

**端末 A から端末 B を操作できる**
**A から B に送られる命令とかデータとかが暗号化されている**

というもの。リモートで他端末にログインしていろいろコマンド実行ができるということです。その際、通信内容は暗号化、つまりガードされていて盗み見はできなくなっています。リモートログイン自体は telnet というものでもできるのですが、telnet は暗号化がされていないので通信が丸見えです。ここが違う点。セキュアです。うれしい

![1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/42a491f4-79f2-b294-87de-7a7888e7c764.png)


# SSH の手順

SSH はざっくりつぎのような手順で接続されます。以下では端末 A から端末 B に SSH 接続するとします。「SSH 接続する」とは「操作可能な状態になる」という意味だと思ってください。

① A と B の間で **鍵交換** が行われ、暗号化で使う情報を共有する
② ①と同時に **ホスト認証** が行われ、B が本物の B であることを A が確認する
③ 通信を暗号化しながら **ユーザー認証** が行われ、B が A を信頼し、接続を許可する

これらについて詳しくかつざっくりに自分なりに理解した点をまとめたいと思います。

![2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/4c9fbb19-7dc0-bc35-3548-5d166418d1d9.png)


# 用語の確認

と、説明に入る前に、このあと◯◯鍵というのが大量に出てきます。これらを区別したいので先に書いときます。出てくる鍵は以下の通りです。

- B のホスト公開鍵
- B のホスト秘密鍵
- A の鍵交換用公開鍵
- A の鍵交換用秘密鍵
- B の鍵交換用公開鍵
- B の鍵交換用秘密鍵
- A のユーザー認証用公開鍵
- A のユーザー認証用秘密鍵

そもそも **鍵** とはテキトーな文字列や数字で、主に **秘密鍵がランダムに生成** されます。そして、適当なアルゴリズムによって **秘密鍵から公開鍵が作成** されます。このアルゴリズムには RSA とかがあって RSA の中にもいろんな方式があります。

**ホスト公開鍵** と **ホスト秘密鍵** とは B がもともと持っているものです。これらを **ホストキー** と呼びます。なお、B のことをホストと呼びます。B が SSH の設定をした時点でホストキーは自動的に作成され、リセットしない限りずっと同じものになります。

**鍵交換用** とあるのはこのあと説明する①の「鍵交換」で使う鍵です。これは **一時的** なものになります。
**ユーザー認証用** は③の「ユーザー認証」で使うものです。

なお、よくいう「SSH の公開鍵認証」の公開鍵は「A のユーザー認証用公開鍵」のことになります。ユーザー認証用の鍵は「**認証キー**」とか「**認証鍵**」とか呼んだりします。

# 鍵交換
「鍵交換」とは **暗号化で使う情報を公開鍵と秘密鍵を用いて共有・決定すること** です。

暗号化と言ってもいろいろなやり方があります。たとえば、シーザー暗号（アルファベットを何文字かずらす）で暗号化しよう！ずらす文字は 5 文字にしよう！みたいなことです。しかし、この段階ではまだ暗号化されていません。なので単に共有しただけではこの「5 文字ずらす」みたいなのがバレてしまいます。これではまずいので「5」がバレないように情報共有をしていきます。（なお、ここでいう「暗号化」は SSH 接続後（正確にはユーザー認証時も含み、上の③以降）の通信を暗号化することとします。）

超ざっくりいうと、**お互いに鍵のペアを作って、片方だけ交換。こうしてできた新しい鍵のペアからお互いが個別に共通の「暗号化の素材」を導き出す**、って感じ。暗号化の素材とは「5」のこと。公開鍵しか交換しないので「5」がバレることはありません（数学的に難しいことをしているし、鍵のペアが変わっても同じものが出てくるというのも数学的なしくみ）。

![3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/6ea2ad11-9d38-d233-0632-515c35d403d2.png)


さて、詳しく鍵交換は次の手順で行われます。

① どの方式で鍵交換するか、どの方式で暗号化するかなどを決める（**Key Exchange Init**）
② A が一時的に作った鍵交換用公開鍵を B に送付する
③ B はもらった公開鍵をもとに署名を作成し、A に送る
④ A はもらった署名を検証する（ここでホスト認証を同時に行う）

![4](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/b2528cf4-040b-7deb-ad0b-f4266fc1d905.png)


### ① Key Exchange Init

まず、A と B の間で通信を始める前に、これから **どんな感じでやりとりをするかを決定** します。次の 5 項目を決めます。具体的には僕はこんなやり方ができるよ～というのをお互いに提示します（ネゴシエーション）。

<dl>
<dt><!-

鍵交換のアルゴリズム</dt><dd>鍵交換をどうやってやるかを決める。`curve25519-sha256` `ecdh-sha2-nistp256` `diffie-hellman-group18-sha512` などある。よくわかってないけど。
ちなみに鍵交換には主に RSA 系と DH 系という 2 種類の方式があるが、SSH では DH 系を使っている。さっき上げた 3 つのアルゴリズムはおそらく DH 系の中でやり方が違うんだと思う。</dd><br>
<dt><!-

ホストキーの生成アルゴリズム（署名に使うアルゴリズム）</dt><dd>ホストキーには何種類かがあり、これのどれを使うかを決める。`rsa-sha2-512` `ecdsa-sha2-nistp256` `ssh-ed25519` などがある。鍵交換にはホストキーを使う（署名用として）ので、どのアルゴリズムで作ったホストキーを使うかを決めとかないとならない！</dd><br>
<dt><!-

暗号化のアルゴリズム</dt><dd>これからの通信をどんな感じで暗号化するか決める。さっきだと「シーザー暗号！」みたいな話。こんな単純ではなく `chacha20-poly1305` `aes256-ctr` などから選ぶ。いわゆる共通鍵。</dd><br>
<dt><!-

MACs のアルゴリズム</dt><dd>MACs とは暗号化されたメッセージが改ざんされていないかを確認するもので、その方式を決める。`hmac-sha2-256` `umac-64-etm` など</dd><br>
<dt><!-

圧縮のアルゴリズム</dt><dd>どの方式で圧縮する決める。しないこともある。`zlib` とか
</dl>

### ② A が鍵交換用公開鍵を送付

どんな感じでやり取りするかが決まったら、お互いが鍵交換用の鍵を生成し、公開鍵を交換して、「暗号化の素材」を生成することが始まります。

まず、A が①で決めた鍵交換のアルゴリズムを使って秘密鍵と公開鍵のペアを作り、**公開鍵のみを B に送信** します。

>具体的には、A がランダムに $x$ を生成します。これが秘密鍵です。これから $e=g^x\ \rm{mod}\ p$ を計算します。$g$ や $p$ は選択した鍵交換のアルゴリズムで決定します。この $e$ が公開鍵で B に送信します。（より詳しい話は DH 鍵共有の仕組みを調べてください。私もあんまり理解できてないですが）。

### ③ B が署名を作成

B 側は A と同じように鍵交換用の秘密鍵・公開鍵のペアを作っています。この「B の鍵交換用秘密鍵」と「もらった A の鍵交換用公開鍵」を使って「**暗号化の素材**」を作ります。これは、これからの通信を暗号化する際に使うものです。さっきでいうと「5」です。

>より詳しくは B でもランダムに $y$ を生成（これが B の鍵交換用秘密鍵）して $f = g^y\ \rm{mod}\ p$ を計算（これが B の鍵交換用公開鍵）。もらった $e$ から $K= e^y\ \rm{mod}\ p$ を計算する。この $K$ が暗号化の素材

この段階で「B の鍵交換用公開鍵」を A に送ってしまえばお互いに「暗号化の素材」を獲得できますが、まだ暗号化はされていないので鍵交換用公開鍵が本物かわかりませんし、暗号化の素材もお互い一緒かはわかりません。これを検証するために B は **署名** を作って A に送ります。

「暗号化の素材」「A と B の情報」「B の鍵交換用公開鍵」「A の鍵交換用公開鍵」「ホスト公開鍵」の内容から「ホスト秘密鍵」を使って **署名** を作成します。署名とは説明が難しい（あんまりよくわかってない）ので、とりあえずこれらをハッシュにしたりなんだりして、B がもともと持っている秘密鍵（ホスト秘密鍵）を使って（①で決めた署名に使うアルゴリズムにもとづいて）署名を作るもの、だと思ってください。実際はもっと複雑っぽくてむずかしい！「暗号化」「復号」という言葉を安易に使ってはいけない

> 「A と B の情報」とは①でネゴシエーションするときにお互いが提示したアルゴリズムの種類とか、A の名前、B の名前とかのこと。

この「署名」と「B の鍵交換用公開鍵」「ホスト公開鍵」の 3 つをまとめて A に送ります。

### ④ A が署名を検証する

ここでは次の 3 点を確認します。

- 送られてきたホスト公開鍵が本当に B のものか
- 暗号化の素材はお互い同じものが得られているか
- B はホスト公開鍵に対応するホスト秘密鍵をちゃんと持っているか

B から「B の鍵交換用公開鍵」が送られてきたので、A も同じように「暗号化の素材」を計算します。さらに同じように署名を作成します。ただし、署名にはもらった「ホスト公開鍵」を使います。こうして作った署名ともらった署名と一致するかどうかを見れば上の 3 点が分かるのです（鍵の仕組みとして公開鍵で署名を作るのと秘密鍵で署名を作るのは同じものが出てくる）。

なお、このタイミングで後述する「**ホスト認証**」を行っています。簡単に言うと、B が本物の B であるかを照合します。


<br>

以上のようにして鍵交換が行われて、「暗号化の素材」の共有と、その信頼性が示されました。

# ホスト認証

ここではホスト認証のしくみを簡単に説明します。

まず「**ホスト認証**」とは、**B が本物の B であることを確認する** もので「B に接続しようとしてるけどこの B って私（A）が知っている B で合ってるかな」を調べます。

具体的には「ホスト公開鍵」が送られてきたときに、A は自分がもっている情報を見て「知ってる公開鍵だ！」というのを確認します。これは「なりすまし」を防ぐ効果があります。B になりすまそうとしている C がいたとして、C は B のホスト公開鍵が分かりません。なのでテキトーに作って A とやり取りしようとしますが、A は B のホスト公開鍵を知ってるので見破られてしまいます。

初めて SSH 接続するときはもちろん「ホスト公開鍵」を知らないので、こんなことが起きます。

```
PS C:\Users\user> ssh hogehoge.com 
The authenticity of host 'hogehoge.com' can't be established.
ED25519 key fingerprint is SHA256:LlpKsNwOj8C7vlwL4l+f53e6ccfXJpLHh9C+QcvsUxk.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
```

これは `hogehoge.com` に初めて SSH 接続しようとしたときで「信頼性がありません」と言われています。3 行目に書かれているのは送られてきた「ホスト公開鍵」です。ハッシュされています。この公開鍵、俺（コンピューター君）は知らないけどお前（コマンド打ってるお前）はどう思う？ yes/no ということです。
つまりは、SSH したいサーバー側（B 側）で実際に生の「ホスト公開鍵」を確認して合ってるかどうか見て！ってこと。普通は黙って yes すると思うけど。

ここで yes すると `~/.ssh/known_hosts` に「ホスト公開鍵」が記録されて、2 回目以降の接続にはここを見て「知ってる子だ！」というのを判断しているって感じです。

```
PS C:\Users\user\.ssh> cat .\known_hosts
hogehoge.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhGNERQLFU5xPOP45mxYEvWHsueBI8rtM0nfXhwAc1Z
```

なお、一度接続に成功すると自動的にすべての「ホスト公開鍵」が記録されます。

ちなみに、もし送られてきた公開鍵が `known_hosts` と違った場合はこのような警告が出ます。

```
PS C:\Users\user> ssh hogehoge.com 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
```

ちゃんと「違うよ！」と教えてくれる。いい子。これが「ホスト認証」の仕組みです。「ホスト公開鍵」を使うので「鍵交換」の最中についでに行われます。

# ユーザー認証

さて、鍵交換が終わって暗号化のやり方も決まった！ホスト認証もして B が本物って分かった！いよいよ B をいじくり回しましょう。

まず、SSH はほかの端末に入り込んでコマンド実行ができるとはいえ、むやみに入られては困ったことになってしまう！たとえば、あなたのお家の玄関には鍵がなくて、誰でも自由に入れる公衆施設みたいになっていたとしたら、これではあなた以外の誰でもお家にあるものだったり家具の配置だったりいろいろ簡単にいじれてしまいます。

つまり、入口には鍵をかけていないといけないわけで、鍵を持っている人が自由に出入りできる。これが SSH では「**ユーザー認証**」というふうに言います。「ユーザー認証」は「**パスワード認証**」と「**公開鍵認証**」の 2 つの方式があります。うわ、公開鍵、また出てきたよ。ただざっくりいうと

<dl>
<dt>パスワード認証</dt><dd>あいことばを知っていればおうちに入れる</dd>
<dt>公開鍵認証</dt><dd>鍵をもってればおうちに入れる</dd>
</dl>

こんな感じです。ひとつひとつ説明します。

### パスワード認証

名前の通りパスワードで玄関を開けます。これはサーバー側で事前にユーザーを決めて、クライアントがユーザー名とパスワードを伝えます。
イメージとしては A が B に [ID: hoge, pass: 1234] というユーザーを作ったとします。A が B に SSH しようとして、

B < どのユーザーでログインするの？
A < hoge
B < パスワードを教えて
A < 1234
B < OK!

こんな感じ。秘密基地に入るときに秘密基地の利用メンバーとそれぞれの合言葉みたいなのがあって、「あなたは誰ですか？」「合言葉は？」と聞いているみたいなものです。

ユーザーを特に作っていない場合だと root でログインします。責任者みたいな。

このパスワード認証ですがパスワードは使いまわしや打つのがめんどうだったりするのであまり推奨されていないと思います。

### 公開鍵認証

事前にクライアント A がキーペアを作っておいて、公開鍵を B にあらかじめわたしておき認証を行うやり方です。イメージとしては勘合貿易みたいな感じで、片割れを相手にあげといて、接続しようとしたときに合致するかを見ています。

具体的には次のような手順になります。認証キーとはこの公開鍵認証で使う公開鍵と秘密鍵のことです。以降、認証用公開鍵、認証用秘密鍵などと呼びます。

⓪ A は認証キーを作成し、認証用公開鍵を B に知らせておく
① A は署名を作成して、B に送信する
② B は署名を検証して、OK なら認証成功！

![5](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810010/5fa21b7e-ef99-5b10-5c9f-bf6d5ccce28f.png)


#### ⓪ 認証キー作成

これは事前にやっておくことになります。いろんな鍵生成の方式がありますが `ed25519` が主流のようです。秘密鍵と公開鍵が作られますが、**公開鍵のみ** アクセスしたいサーバーに教えておきます。自分が誰であるかのユーザー情報ももちろんセットで教えておきます。

#### ① 署名作成

ここからは一般的な公開鍵暗号方式、電子署名と同じです。

A 自身が本物であることを証明するために署名を作って B に検証してもらいます。
署名は「セッション ID」「ユーザー情報」「認証用公開鍵」「付加情報」をハッシュし、認証用秘密鍵で作ります。この「署名」と「ユーザー情報」「認証用公開鍵」「付加情報」を一緒に B に送付します。

「**セッション ID**」とは鍵交換のときに得られた「暗号化の素材」を使って作られるものです。推測されにくいものを作ります。「ユーザー情報」「付加情報」はおまけの情報で、ユーザーの名前や SSH サービスの種類やバージョン、認証キーのアルゴリズムなどが含まれています。

#### ② 署名検証

まず、B がもらったユーザー情報と認証用公開鍵を⓪であらかじめ受け取っていたものと照合します。その後、同じように署名を作成します。ただし、認証用秘密鍵は B は持っていないので、あらかじめ受け取っていた認証用公開鍵を使って作成します。これが一致するかを確認して、大丈夫であれば接続を許可する、という感じです。

<br>

ざっくりこんな感じで公開鍵認証が行われます。まあデジタル署名と同じ。ちなみに、ランダム文字列を作って暗号化と復号はおかしい説明らしい。ちゃんと署名を使っています。

# まとめ

SSH はこうやってつながっている！というのを確認しました。もっかい確認すると

① 鍵交換で暗号化のやり方を決める
② ①と同時にホスト認証も行う
③ ユーザー認証を行う

<dl><dt>
鍵交換</dt><dd> それぞれがキーを作って公開鍵のみ交換し、「暗号化の素材」を作る。署名も作って検証する</dd>
<dt>ホスト認証</dt><dd> ホスト公開鍵でサーバーが本物か確認</dd><dt>ユーザー認証</dt><dd>パスワードや公開鍵でクライアントの接続を許可する</dd></dl>

暗号化の仕組みはむずかしい！！けどすごい！！ということがわかりました

# 参考
以下の記事を参考にしました。

[SSHについて理解できたとこまでまとめてみた](https://qiita.com/Asari_/items/5192525ebc3de9ac63e5)
[SSHのハイブリッド暗号に関する良くある誤解の話](https://qiita.com/angel_p_57/items/30a12a0d45457b5f76d5)
[SSHの公開鍵ってなに?](https://qiita.com/angel_p_57/items/19eda15576b3dceb7608)
[実は8種類の鍵が使われている！？｜SSH接続の流れ・仕組み](https://zenn.dev/isosa/articles/77a70eb84f3ec2)
[SSHの公開鍵認証における良くある誤解の話](https://qiita.com/angel_p_57/items/2e3f3f8661de32a0d432)
[[暗号技術入門] 鍵共有・鍵交換](https://tex2e.github.io/blog/crypto/key-exchange)

# 更新履歴
- 2024/07/15
    - 図を追加して、微修正