SSHのハイブリッド暗号に関する良くある誤解の話

# はじめに
## 背景
以前の「[SSHの公開鍵認証における良くある誤解の話](/angel_p_57/items/2e3f3f8661de32a0d432)」に引き続き、SSHにまつわる誤解に関する話です。
公開鍵暗号とハイブリッド暗号のありふれた説明を聞くと、多くの人が「SSH? 共通鍵を公開鍵暗号で暗号化して渡すのね、オーケーオーケー」と早合点しがちです。しかし実態は全く異なります。
その点を少し具体的に解説します。

## TL;DR;

* SSHもSSL/TLSと類似の、鍵交換・認証(ホスト認証)・暗号化・MACの4要素のハイブリッド暗号
* 2種類の公開鍵暗号、鍵交換(鍵交換用)、電子署名(ホスト認証用)が連携して使われる
* 「公開鍵で暗号化」は基本使われない。知識として忘れた方がマシ

## 注意事項

今回はユーザ認証 ( パスワード認証や公開鍵認証など ) の話は対象外です。
また、SSHと言っても既に過去の存在の SSHv1については対象外です。全て SSHv2 の話です。

なお、動作検証は、x86_64 Windows10 上で、クライアントは WSL1/Ubuntu18.04/OpenSSH 7.6p1 で行っています。

# ハイブリッド暗号
## 構成要素
暗号化通信としてポピュラーな技術として SSL/TLS があり、鍵交換・認証・暗号化・MAC(メッセージ認証コード)の4要素のハイブリッドになっています。
※詳細は「[SSL/TLSの基本](/angel_p_57/items/446130934b425d90f89d)」で解説しています。

SSH も同じようにこの4要素で構成されており、技術的には大分似ています。

* 鍵交換
  * 暗号化・MACで使う共通鍵の元になる秘密情報を共有する機能
  * DH,ECDHといった鍵交換(公開鍵暗号の1種)を使う
* ホスト認証
  * 接続したサーバが意図通りか ( なりすましでないか ) を確認する機能
  * RSA署名,ECDSA,EdDSAといった電子署名(公開鍵暗号の1種)を使う
  * 「**正しい署名を作れるのは秘密鍵の持ち主だけ**」という性質を利用し、署名によって本物のサーバであることを証明する
  * クライアントがサーバを識別するキーとなるのはホスト公開鍵
* 暗号化
  * 通信データを暗号化し盗聴を防ぐ機能
  * AES,ChaCha20といった共通鍵暗号を使う
* MAC(メッセージ認証コード)
  * 通信データに改ざん検知用の確認データ(メッセージ認証コード)を付与する機能
  * 現在は、AEADとして暗号化機能がMACも兼ねる方式が一般的
  * AEADとして、AESのGCMモード,ChaCha20-Poly1305等が使われる

## SSL/TLSとの対比
以下は、「[SSL/TLSの基本](/angel_p_57/items/446130934b425d90f89d)」で挙げた図です。SSH についても機能の位置づけは同じとなります。

![SSL/TLSの機能関連図](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F74396%2Fb9b89607-6996-84b9-dae8-7fe5b7639d62.png?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&w=1400&fit=max&s=c4f82d28c97e1959cdcdaab5c7f4e518)

ただ、SSL/TLSとSSHで**大きく異なる点が1つあります**。
SSL/TLSについては、サーバが持つ公開鍵を必ず証明書の形で扱い、PKIによって「どのサーバの公開鍵か」を保証しますが、SSHでは一般に**裸の公開鍵を直接扱い**、「どのサーバがどんな公開鍵(ホスト鍵)を持っているか」を**確認する責任はクライアントが負う**ことになります。
※なおSSHの主要実装であるOpenSSHでは、SSL/TLSで使うX509形式ではありませんが、裸の公開鍵の代わりに証明書を使う運用も可能になっています。

# 鍵交換とホスト認証の連携
## 連携する理由
上で挙げた通り、SSHでは鍵交換で「鍵交換」・ホスト認証で「電子署名」2種類の公開鍵暗号を用います。
しかしこの2つはバラバラに機能するわけではなく連携したものとして、同じ処理の中でまとめて使われます。
その理由もSSL/TLSと同様です。

* 鍵交換からの視点
  * 鍵交換に関わるデータに署名をつけることによって保証を与え、第三者によるすりかえを防ぐ
* ホスト認証からの視点
  * 署名の対象となるデータに鍵交換のデータを絡めることで、署名の流用を防ぐ  
    ※署名データだけなら第三者も取得可能だが、鍵交換を成立させるデータが揃わないので役に立たない

このように、両機能がお互いの弱点を補う形になっているのです。

## 処理のイメージ
では、鍵交換・ホスト認証がどのように実施されるのか。イメージとしては次の図のようになり、**接続開始時にほぼ真っ先に実施**され、その後暗号化通信が開始されます。

2種類の公開鍵暗号に絡んで、鍵交換用の秘密鍵・公開鍵の鍵ペアが2組 ( 図中「交C」「交S」で表記 )、ホスト認証用の秘密鍵・公開鍵の鍵ペア ( ホスト鍵、図中「ホスト」で表記 ) が用いられます。
※サーバは予めホスト鍵を用意しておきます
※2023/4/16 図中の「ホスト鍵確認」の位置が「署名検証」と逆に間違えていたため、図を差し替えました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74396/63ff83bb-b00b-9ac4-dff4-53e09a7dfae5.png)

この図にあるように、細々したやりとりを除けば、公開鍵の送付(クライアント→サーバ)と、返送(サーバ→クライアント)の**一往復だけで処理が完了**します。

処理のポイントをかいつまんで挙げると次のようになります。

* 鍵交換
  * お互いが自分の秘密鍵と相手の公開鍵を組み合わせることで、同一の秘密情報を共有します  
    ※参考:「2つの公開鍵暗号」の「[鍵交換](/angel_p_57/items/897bf94160be8d637585#%E9%8D%B5%E4%BA%A4%E6%8F%9B)」の章
  * 共有した秘密情報は、暗号化/MAC用の共通鍵などを作るのに利用されます
  * 公開鍵がネットワーク上を流れますが、第三者が盗聴しても同じ秘密情報を作成することができません
* 署名対象
  * 鍵交換用のデータも絡めて、両者で同一のデータを作り、署名対象とします
  * サーバではホスト秘密鍵による署名の生成、クライアントではホスト公開鍵による署名の検証に使用します
* 署名検証
  * 検証が成功すれば、サーバが対応するホスト秘密鍵を持っている証明になります
  * また、署名対象には鍵交換で作成した秘密情報も絡むため、**同一の秘密情報を共有できた**ことも同時に分かります
* ホスト鍵確認
  * サーバのホスト公開鍵が意図通りか、クライアント側で管理している known_hostsファイル等で確認を行います
  * 情報未登録であれば、ここで登録確認が行われるのが一般的です  
    ※正しいホスト鍵かどうかの確認は別途行う必要があります

## 実際の動作例
上で説明した処理内容ですが、実際にSSHを使っていても表から見えるものではないため、それだけで実感することは難しいと思います。
「公開鍵で共通鍵を暗号化して送る」という**デマが後を絶たない**のも、実際に見て確認し辛いからという面もあるでしょう。

そこで、[WireShark](https://www.wireshark.org/download.html)で通信がどう見えるか、実際にキャプチャしたものを載せます。
`ssh -T git@github.com` というコマンドで、githubに初回接続し、`Are you sure you want to continue connecting (yes/no)?` というプロンプトが出るまでの部分になります。

* 公開鍵送付(クライアント→サーバ)  
  画像にある e というパラメータで鍵交換用の公開鍵を送付しているのが分かります  
  なお、DH と書いてありますが、今回の通信ではECDHの一種Curve25519が使われています  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74396/267e2310-8c5e-11a0-ba1f-bfafd53b2e6d.png)
* 公開鍵返送(サーバ→クライアント)  
  画像にある f というパラメータが鍵交換用の公開鍵、それに加えてホスト公開鍵、署名の3点が揃って返送されているのが分かります  
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74396/28fb10e3-e00c-cbc6-457d-2a6ecf09f9c8.png)

コマンドに対して、冗長化オプション`-v`を3重につけたデバッグログでは次のように対応します。

```shell-session:sshデバッグログ抜粋
$ ssh -vvvT git@github.com
(略)
debug1: Connecting to github.com [52.69.186.44] port 22.
(略)
debug3: send packet: type 20                    ← 事前の情報共有(KEXINIT)
debug1: SSH2_MSG_KEXINIT sent
debug3: receive packet: type 20
debug1: SSH2_MSG_KEXINIT received
(略)
debug1: kex: algorithm: curve25519-sha256          ← 鍵交換アルゴリズム(ECDHの一種)
debug1: kex: host key algorithm: rsa-sha2-512      ← ホスト鍵の署名アルゴリズム
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug3: send packet: type 30                       ← 公開鍵送付(KEX_ECDH_INIT)
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug3: receive packet: type 31                    ← 公開鍵返送(KEX_ECDH_REPLY)
debug1: Server host key: ssh-rsa SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8
debug3: hostkeys_foreach: reading file "/home/angel/.ssh/known_hosts"  ← known_hostsの確認
debug3: hostkeys_foreach: reading file "/home/angel/.ssh/known_hosts"
The authenticity of host 'github.com (52.69.186.44)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)?      ← ホスト鍵未登録のためユーザに確認
```

最初に情報を交換して、鍵交換にCurve25519、ホスト認証にRSA署名、暗号化/MACにChacha20-Poly1305を使うことを同意したのち、公開鍵送付(メッセージタイプ30番)、公開鍵返送(メッセージタイプ31番)を行い、known_hostsでのホスト鍵確認を行っていることが分かります。

流石に秘密情報がどうなっているか、そういった点は表に出ませんが、通信内容からでもウラを取ることはある程度可能かと思います。

# 余談
## 公開鍵暗号の説明のマズさ
一般に公開鍵暗号の説明がある場合、「共通鍵暗号とは～、それに対して公開鍵暗号は～」という説明が必ずと言って良いほどあります。
しかし、この記事で見た通り、**そんな説明を覚えてもSSHの理解には全く役に立ってません**。

つまるところ、一般に見る「公開鍵で暗号化して秘密鍵で復号します」という説明は、**公開鍵暗号の基礎ではない**と言えます。
もっと基礎的な部分としては、「2つの公開鍵暗号」の「[概要](/angel_p_57/items/897bf94160be8d637585#%E6%A6%82%E8%A6%81)」で挙げたような特徴の方が適切と言えるでしょう。

マトモな理解を目指すのであれば、一旦「公開鍵で暗号化して～」という説明は、むしろ**忘れてしまった方が良い**と思います。

# おわりに
公開鍵認証と違い、ホスト認証・鍵交換は若干図にし辛いのでためらっていたのですが、折角なので作ってみました。たった一往復のやりとりで処理を完結する様は、結構芸術的で個人的には好きです。
この記事や関連する記事等々で誤解が解消されていけばなあ、と思っています。