LoginSignup
18

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

Last updated at Posted at 2020-12-31

はじめに

背景

以前の「SSHの公開鍵認証における良くある誤解の話」に引き続き、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の基本」で解説しています。

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

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

SSL/TLSとの対比

以下は、「SSL/TLSの基本」で挙げた図です。SSH についても機能の位置づけは同じとなります。

SSL/TLSの機能関連図

ただ、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

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

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

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

実際の動作例

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

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

  • 公開鍵送付(クライアント→サーバ)
    画像にある e というパラメータで鍵交換用の公開鍵を送付しているのが分かります
    なお、DH と書いてありますが、今回の通信ではECDHの一種Curve25519が使われています
    image.png
  • 公開鍵返送(サーバ→クライアント)
    画像にある f というパラメータが鍵交換用の公開鍵、それに加えてホスト公開鍵、署名の3点が揃って返送されているのが分かります
    image.png

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

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つの公開鍵暗号」の「概要」で挙げたような特徴の方が適切と言えるでしょう。

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

おわりに

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18