📝

実は8種類の鍵が使われている!?|SSH接続の流れ・仕組み

2024/05/08に公開
4

この記事ではSSHがどんな流れで安全な接続を行なっているのかをまとめてみます。具体的なコマンドや接続するための手順ではなく、もう少し根本的なSSHという仕組み自体の話になります。SSH接続自体も全体を理解しようとすると結構複雑なのでまずはこの記事でざっくりイメージしてリンク先のページを参照していただくと理解が進むかな?と思います。
※鍵認証についての記事になります。パスワード認証については解説しておりませんので予めご了承ください。

前提

VPSやクラウドなどのサービスを利用する場合での流れを説明しています。自分でUbuntuなどのLinuxOSをインストールしてサーバを構築する際にはもう少し手順が増えるので適宜調査してみてください。

学習の注意点|鍵の呼び方について

個人的にはSSHを調べていると鍵が様々な呼ばれ方をするのでよくわからなくなります。人間みたいなものですね。「日本人」「静岡県人」「社長」「お兄さん」「大人」など一人の人も場面によっていろいろな呼び方をされますが、鍵においてもそんなイメージがあることを最初に理解するのが結構大事だと思いました。そこでまずざっくりと特徴についての呼び方と、使用用途についての呼び方があるのを整理してみます。

特徴的名称

「共通鍵」「公開鍵」「秘密鍵」これらは文字通りの特徴を持ちます。
共通鍵:クライアントとサーバが共通の鍵を持ちます。
公開鍵:公開しても良い鍵のことです。
秘密鍵:秘密にしなければならない鍵です。

使用用途を元にした名称

「ホスト鍵」「ユーザー鍵」「セッション鍵」これらは使用用途を表しています。
ホスト鍵:ホスト認証に使われます。
ユーザー鍵:ユーザー認証に使われます。
セッション鍵:セッション(通信)の暗号化に使われます。

そして実際の鍵は次のように複数の要素を持つ形で存在します。
「公開ホスト鍵」「秘密ユーザー鍵」
詳細は後述のイラストで見た方がわかりやすいと思いますのでまずはこんな呼び方があるんだな〜という理解で大丈夫です。

SSHとは

SSH(Secure Shell)とはネットワーク上のコンピュータに手元のハードウェアから接続して遠隔操作するための通信プロトコル、ソフトウェアです。よくサーバに接続して作業するなどと言われますがあれはSSHを使用していることが多いですね。

何に対してSecureか?

Telnetと比較すると
・通信データの盗聴
・通信相手のなりすまし
・通信データの改竄
を防ぐ仕組み(通信内容の暗号化)を備えています。

SSHがSecureを確保するためにしていること概要

・セッション中の通信を暗号化(共通鍵)
・クライアントPCが通信相手を本物のサーバなのか確認(公開鍵と秘密鍵のペアその1。このペアはサーバ認証|ホスト認証に使われるのでホスト鍵と呼ばれます。)
・サーバが通信相手を本物のクライアントPCなのか確認(公開鍵と秘密鍵のペアその2、これがユーザー鍵と呼ばれます)
つまり通信する者同士それぞれが相手を確認しあって本物の通信相手かどうかを確かめています。
これによって安全に正確な相手と通信することが可能になります。

SSH接続する流れ

接続する際の流れですが「私たちが行う見える部分」と、「SSHコマンドがクライアントPCとサーバ(PC)との間で自動的に裏側で行ってくれている部分」がありますので大きく分けて解説します。

私たちが行う部分

1.クライアントPCで秘密鍵、公開鍵を作成します。これがユーザー鍵と呼ばれます。


通常はssh-keygenコマンドで作成されます。これは後述するクライアント認証に使われる公開鍵と秘密鍵のペアその2です。この鍵はユーザー鍵と呼ばれることがあります。

2.接続したいサーバに公開鍵を保存します。


具体的には通常サーバの特定ユーザーの~/.ssh/authorized_keysファイルに追加されます。ここまでで事前準備は完了です。

3.クライアントからサーバへ接続したいという信号を送ります。(SSHコマンドですね)


コマンド例)ssh username@server_address
ここまでが私たちが手を動かすところになります。

SSHが自動的に行ってくれている部分|SSHプロトコルの安全な接続プロセス確立

以下はクライアントとサーバ側で自動的に裏側で動いてくれる内容を解説します。
ここも大きく2つに分かれます。4鍵交換と5クライアント認証です。鍵交換中にホスト認証と共通鍵(セッション鍵)の導出が行われています。

4.鍵交換(Key Exchange)

この鍵交換にはプロセスが四個あります。

一.利用アルゴリズムの決定

クライアントとサーバで使用するアルゴリズムを決めます。具体的なルールも定められているようですので興味のある方はRFC4253−section7.1をご確認ください。

二.秘密の値の共有|DH鍵交換開始

ここからDH鍵交換が始まります。まず秘密の値を共有します。
クライアントでランダムな値xを決めます。そして以下の計算式に当てはめます。

e = g^x\enspace mod\enspace p

このeという値をクライアントからサーバに送信します。
詳細はRFC4253-section8冒頭手順の1をご確認ください。

三.サーバ認証|ホスト認証

SSHにおいてサーバ認証はDH鍵交換の途中で行われます。詳細は後述するプルダウンから参照ください。

サーバ認証|ホスト認証を簡単にまとめると


クライアントが接続先のサーバが本物かを確認します。sshコマンドで最初にフィンガープリントを聞かれるのもここでのタイミングです。このプロセスはサーバが持つ秘密鍵と公開鍵のペアその1(ホストキー)のうちの公開鍵をクライアントに送付しているということです。サーバ認証をすることで接続しようとしているサーバーが本物であることを保証します。また中間者攻撃、IPスプーフィング、DNSスプーフィング攻撃を防ぐことができるとのことでした。

サーバ認証詳解|より詳しく知りたい方はご参照ください。

以下はRFC4253 section8を元に書いてみました。
サーバ認証は2つのプロセスで行われます。
I サーバがホスト鍵をクライアントに送る
II クライアントがホスト鍵を検証する


I サーバがホスト鍵をクライアントに送る

1.サーバでもランダムな値yを決めます。そして以下の計算式に当てはめます。

f = g^y\enspace mod\enspace p

2.次に二でクライアントから共有された秘密の値eとここまでの通信内容を元にして以下の計算を行います。

K = e^y\enspace mod\enspace p

3.hash関数で前述のfとKを使用して計算してHという値を出します。

H = hash(V\_C || V\_S || I\_C || I\_S || K\_S || e || f || K)

このhash関数に渡されている変数は||によって連結されます。そしてそれぞれ次のような型と意味があるようです。
文字列 V_C: クライアントの識別文字列
文字列 V_S: サーバーの識別文字列
文字列 I_C: クライアントのSSH_MSG_KEXINITメッセージのペイロード
文字列 I_S: サーバーのSSH_MSG_KEXINITメッセージのペイロード
文字列 K_S: サーバーの公開ホスト鍵
mpint e、クライアントから送信された交換値
mpint f、サーバーから送信された交換値
mpint K、共有秘密

4.このHという値がExchange Hashという値になります。サーバーはこのHに自身のホスト秘密鍵を使ってデジタル署名sを行います。署名については以下の記事がわかりやすく解説してくれているのでご参照ください。

https://zenn.dev/herumi/articles/rsa-signature

5.そして最終的に以下の3つが連結されたものがクライアントへ送られます。
K_S||f||s
つまりサーバの公開ホスト鍵とサーバから送信された交換値と署名したデータを結合したものをクライアントに送ります。


II クライアントが公開ホスト鍵K_Sを検証する

クライアントが公開ホスト鍵K_Sが本当にサーバーの公開ホスト鍵であるかを検証します。

1.受け取ったK_S||f||sを「K_S」、「f」、「s」と分けます。
2.「K_S」をknown_hostsか証明書に照らし合わせて本当にサーバーの公開ホスト鍵かどうかを検証します。これらのリストになければフィンガープリントを計算してユーザーに提示し本物かどうかの確認を促します。
ユーザーはブラウザなどからサーバのコンソールで確認します。このフィンガープリントメッセージでYesを選択するとクライアントのknown_hostsにサーバの公開ホスト鍵が保存されます。
そしてサーバから受け取ったfの値を使って以下の計算をします。

3.共有秘密Kをクライアント側でも再度生成します。

K = f^x\enspace mod\enspace p

4.そして今クライアント側で再計算したKとサーバから受け取ったfの値を使ってハッシュ値Hを計算します。

H = hash(V\_C || V\_S || I\_C || I\_S || K\_S || e || f || K)

5.今クライアント側で再計算したHにサーバから受け取った公開ホスト鍵K_Sで署名をします。

6.最後にサーバから受け取ったs(サーバ側で署名したもの)と今クライアント側で署名したものを検証して一致していればホスト認証が完了します。

四.セッションID、各種セッション鍵等の導出


ここで使われているKはホスト認証で計算した値が応用されています。またセッション鍵の計算中にホスト認証で計算したHも使用されてセッション鍵の導出が行われているようです。
詳細はRFC4253 section7.2をご参照ください。

5.クライアント認証|ユーザー認証

サーバが通信の相手が本物のクライアントかを確認します。ここでも公開鍵と秘密鍵のペアその2が使用されています。注意点としてはサーバ認証とはまた別のペアが使われていることになります。(前述の「1.クライアントPCで秘密鍵、公開鍵を作成します」で生成した鍵です)

これでSSH接続(ログイン)できるようになりました。
このあとはセッション(通信)が開始されコマンドなどでサーバを操作します。
そしてセッションを終了、またはタイムアウトするとセッションキーは削除されます。次回接続時には新しいセッションキーが生成されます。
ユーザー鍵、ホスト鍵は自分で更新しない限り同じものを使うという形になります。

もっと知りたい方へ

公開鍵認証という切り口から見たい方へ

今回、公開鍵認証が2つの場面(ホスト認証とユーザー認証)で出てきました。認証についてわかりやすくまとめていただいているので私も参考にさせていただきました。

https://zenn.dev/tetsu1008/articles/8027cbab954e5e

実際に鍵を見てみたい方

EC2で理解するためのハンズオンをまとめてみましたので実際にやってみると理解が進むかもしれません。

https://zenn.dev/isosa/articles/2dfe273e42c638

※上記EC2での記事では変更点が1つあります。前述の「初期設定する流れ」の1と2ではクライアントPC側で鍵のペアを生成していましたが、EC2側で生成する機能があり、それを使うことが多いと思いますのでそちらに変わっている点にはご注意ください。

ホスト鍵はどこからきたのか?

前述の流れでホスト鍵が突然現れたことが気になった方がいるかもしれません。クラウド、VPSなどのサービスでサーバを利用する際には多くの場合ホスト鍵がサーバを立ち上げると生成されているようです。EC2に関しては実際に上記の記事通り進めていただくと確認できるのでお試しください。
なので自分でUbuntuなどのLinux OSから組む場合はユーザー鍵と同じようにssh-keygenコマンドで生成する必要があります。

基本情報技術者試験平成29年秋期午後問1

私自身SSH接続は使っていましたが正直こういった詳細は理解しておりませんでした。この記事を書くきっかけになったのが基本情報技術者試験の過去問平成29年秋期午後問1でサーバ認証とクライアント認証の問題が出てきて、解説を読んでもなんか理解できていない気がする。。。ということがあり、また普段からフィンガープリントの値を確認する時も疑問があり、調査を進めていたらなんとか腹落ちできました。この記事の理解度確認として前述の問題は確認になると思います。

参考

https://zenn.dev/tetsu1008/articles/8027cbab954e5e

https://zenn.dev/tetsu1008/articles/1e3673ca1ece42

https://www.oreilly.co.jp/books/4873112877/

一次情報はあまり読み解けていませんが参考までに一応貼っておきます。

https://www.rfc-editor.org/rfc/rfc4251

https://www.rfc-editor.org/rfc/rfc4252

https://www.rfc-editor.org/rfc/rfc4253

special thanks
IWAMOTO Kouichiさん
丁寧かつ的確なご指摘をいただきこの場を借りて感謝申し上げます。特に後半は私一人ではとても書けなかった記事です。ありがとうございました。
chatGPT

Discussion

いわもと こういちいわもと こういち

・通信データの盗聴
・通信相手のなりすまし
を防ぐ仕組み(通信内容の暗号化)を備えています。

もう一つ、「通信データの改竄」を防ぐ仕組みもありますね。
なぜか軽んじられる事が多いのですが、他の二つと同じくらい大事な機能です。

以下はクライアントとサーバ側で自動的に裏側で動いてくれる内容を解説します。
ここも大きく3つに分かれます。

SSHで鍵交換(Key Exchange)と呼ぶ処理は以下の事を行います[1]

  1. 利用アルゴリズムの決定
  2. 鍵交換プロトコルによる秘密の値の共有
  3. 鍵交換[2]が正しく行われたかの確認 (ホスト認証)
  4. セッションID, 各種セッション鍵等の導出

このように、ホスト認証は鍵交換の一部として行われます。また、ホスト認証では1や2で得た値を利用しますし、ホスト認証で使ったホスト公開鍵も利用して各種セッション鍵の導出が行われるように、ホスト認証は鍵交換の他の部分と密接に関わり合って行われます。
なので、「3つに分かれます」というように別の処理で有るように書かれるのは抵抗が有ります。
"鍵交換"を鍵交換プロトコルの実行の部分を指す意味で使っているのならば分かれるとするのもまだ判りますが[3]、その場合は共有するのは秘密の値一つだけになります。

4.鍵交換 (Key Exchange)

ここでいう"鍵交換"がDH鍵交換等の鍵交換プロトコルの事を指すのならば、前述したように共有する値は一つだけです。

ホスト認証も含めた鍵交換処理全体の事を指すならば最終的に複数の値が共有されますが、その数はここに有るように6つです[4]
この内IVについては通常は鍵とは呼ばないので数えないのはいいのですが、Encryption keyを方向別に数えているのに、同じように方向別に有るIntegrity keyを”安全性の鍵”と一つにまとめるのは数え方がおかしいと思います。

5.サーバ認証|ホスト認証

「公開鍵(ホスト鍵)をフィンガープリントとして提示します」と有りますが、実際に提示されるのはホスト公開鍵そのものです。
そもそもホスト公開鍵やそのフィンガープリントはサーバーに接続出来れば簡単に入手できる公開された物であり、提示するだけならば偽物のサーバーも行えます。
重要なのは一緒に送った署名がホスト公開鍵で検証できる事によって、対応する秘密鍵を所有しているのを証明する事にあります。
フィンガープリントを送ったのでは、このホスト公開鍵による検証が行えません。
また ~/.ssh/known_hosts にはホスト公開鍵が保存される事からもフィンガープリントを送っているわけでは無い事が判ると思います。

6.クライアント認証|ユーザー認証

図ではサーバー側からクライアント側に何らかの要求をするように書かれていますが、実際にはクライアント側が自発的にユーザー認証を要求し、サーバーはそれに対して結果を返すだけです。
極端な状況では、ユーザー側は認証要求を送らずに直接コネクションプロトコルの利用を要求する事もSSHプロトコルでは許されています[5]。(ただし一般的なSSHサーバーでは許可を返さないでしょうが)

クライアントが送るユーザー認証要求では、署名と共にユーザーの公開鍵も一緒に送り、サーバー側はその公開鍵がユーザーのauthorized_keysに登録されているか確認した上で署名を検証するというように、ホスト認証とは方向が逆なだけで本質的には同じ事を行っています。
なので図はホスト認証の処理の方向を逆にした物にするのが判りやすいと思います。

脚注
  1. 参考: RFC4253 7章および8章 ↩︎

  2. 鍵交換プロトコルだけではなく、利用アルゴリズムの決定等を含めた鍵交換処理全体(の内外部との通信部分)を指します。 ↩︎

  3. 鍵交換の終了とホスト認証の為のホスト公開鍵の送信が同じパケットなので、完全に分かれるとも言い難いですが。 ↩︎

  4. 他には鍵交換プロトコルで共有した秘密の値(shared secret)とそこから計算されるセッションIDも有ります。shared secretは鍵交換処理の外では使わない値なので気にしなくてもいいです。セッションIDはユーザー認証等でも使われる事が有りますが、鍵ではなく秘密のデータ扱いです。 ↩︎

  5. RFC4253 10章では、コネクションが確率した後にssh-userauthサービスだけではなくssh-connectionサービスを要求する事も許されています。 ↩︎

takataka

IWAMOTO Kouichiさん詳細な訂正ありがとうございます。
修正してみました。恐れ入りますが認識違いがありましたら
お時間のある時にご指摘いただければ嬉しいです。

また一点、私の前提知識がないために訂正いただいた中で以下の部分が理解できませんでした。

ホスト認証も含めた鍵交換処理全体の事を指すならば最終的に複数の値が共有されますが、その数はここに有るように6つです

このご指摘を受けInitial IV2つ、Encryption key2つ、Integrity key2つが生成されることは理解しEncryption key2つ、Integrity key2は図に盛り込んでみました。これらはホスト認証には使われないという認識ですが間違っておりますでしょうか?。RFC4251〜4253を「host authentication」「server authentication」などのワードで検索してみたのですが具体的な処理の流れなどが見つけられず最後にChatGPTに質問してこの認識となりました。

誠に恐縮ですがお時間が許しましたらまたご確認いただけたら嬉しく思います。
まずは訂正コメントいただきましてありがとうございました。

いわもと こういちいわもと こういち

一.利用アルゴリズムの決定

このステップの詳細については、7. Key Exchange より 7.1. Algorithm Negotiation を参照する方がいいと思います。

二.鍵交換プロトコルによる秘密の値の共有

この段階で行われるのはShared Secretの共有までで、各セッション鍵の導出までは行われません。

三.サーバ認証|ホスト認証

ホスト認証では、まず 二.で共有されたShared Secretやここまでの通信内容を元にして Exchange Hash という値を計算します。
そしてこの Exchange Hash に署名を行う事で、ホスト秘密鍵を持っている事を証明します[1]

二.と三.のステップに対応するのはRFC 4253では 8. Diffie-Hellman Key Exchangeになります[2]

四.セッションID、各種セッション鍵等の導出

ここで二, 三で共有したShared SecretとExchange Hashを使って各セッション鍵の導出が行われます。
7.2. Output from Key Exchange が対応するのはこのステップになります。

これらはホスト認証には使われないという認識ですが間違っておりますでしょうか?。

ホスト認証の段階では各セッション鍵は導出前であり、これらの鍵がホスト認証で使われる事は有りません。
各セッション鍵の導出ではホスト認証で使ったExchange Hashやホスト公開鍵が必要になるため、鍵の導出は基本的にはホスト認証の後に行われる事になります[3]

RFC4251〜4253を「host authentication」「server authentication」などのワードで検索してみたのですが具体的な処理の流れなどが見つけられず最後にChatGPTに質問してこの認識となりました。

RFC 4253の8. Diffie-Hellman Key Exchangeには

The key exchange is combined with a signature with the host key to provide host authentication.

とか

This key exchange method provides explicit server authentication as defined in Section 7.

と有るのですが、どちらもちょうどauthenticationの前で改行されているので見つけづらいかもしれません。

あと、処理の流れがRFC 4253での対応する章だと 7.1 ⇒ 8 ⇒ 7.2 となっている事も理解しづらい原因になっているかもしれません。一応 7.1 には

After the SSH_MSG_KEXINIT message exchange, the key exchange algorithm is run.

とあり、7.2の最初には

The key exchange produces two values: a shared secret K, and an exchange hash H.

と有るので、間に鍵交換アルゴリズムの実行(8章)が有る事がRFCの記述からも読み取れない事も無いのですが。

脚注
  1. 鍵交換方式としてdiffie-hellman-group1-sha1を使った場合。他の方式の場合は違う可能性も有るが、大体の方式がこの手順になっている。 ↩︎

  2. 鍵交換方式としてdiffie-hellman-group1-sha1もしくはdiffie-hellman-group14-sha1を使った場合。他の方式を使った場合はその方式を定めているRFC等に従う。 ↩︎

  3. ただしExchange Hashの計算まで行えばセッション鍵の導出が可能なので、実装によってはホスト認証とセッション鍵の導出が並行して行われる事も有り得る。 ↩︎

takataka

IWAMOTO Kouichiさん度々の訂正ありがとうございます。
また、お手数をおかけしまして申し訳ありません。再度修正してみました。

あと、処理の流れがRFC 4253での対応する章だと 7.1 ⇒ 8 ⇒ 7.2 となっている事も理解しづらい原因になっているかもしれません。一応 7.1 には

こちらまさに予想された通りで見てはいたのですが、番号と相まって理解と整理ができませんでした。
ご指摘いただいた点と、RFCを見直しながら修正してみたのですが認識違いがありますでしょうか?
認識違いがある場合、誠に恐縮ですがお時間が許しましたらまたご指摘いただければ嬉しいです。
丁寧な補足ありがとうございました。