はじめに
「デジタル署名」は、データが特定の秘密鍵を使って署名されたことと、署名後にデータが改ざんされていないことを検証する仕組みで、SSHのログインやHTTPSの通信、ソフトウェアの配布、電子契約など、あらゆる場面で使われています。
デジタル署名では、公開鍵暗号の鍵ペア(秘密鍵・公開鍵)を使います。
- 秘密鍵: 持ち主だけが保管し、誰にも渡さない鍵。署名を作るときに使う
- 公開鍵: 誰に配ってもよい鍵。署名を検証するときに使う
2つの鍵は数学的に対応していますが、公開鍵から秘密鍵を逆算することは事実上不可能です。このため、公開鍵を広く配っても安全です。
公開鍵と秘密鍵を使えば、データに署名したり、その署名を検証したりできます。しかし、検証に成功しただけでは、相手が本当に目的の人物やサーバなのかは分かりません。
そこで重要になるのが、 「その公開鍵は誰のものなのか」 を確認する仕組みです。
この記事では、SSH(ユーザ認証・サーバ認証)とHTTPSを例に、署名の仕組みと公開鍵を信頼する方法を整理します。
デジタル署名の仕組み
署名の生成と検証
- 送信者はデータのハッシュ値を計算する
- ハッシュ値を秘密鍵で署名し、署名値を作る
- データと署名値をセットで送る
- 受信者は受け取ったデータから、同じ方法でハッシュ値を計算する
- 受信者は、公開鍵を使って署名を検証し、署名がそのデータに対して正しいかを確認する
データが1文字でも書き換わるとハッシュ値が変わるため、検証に失敗します。これが改ざん検知できる理由です。
残る問題: 署名の検証は成功するのに、なりすましが成立する
署名の検証で分かることは、実は次の1点だけです。
この署名は、この公開鍵に対応する秘密鍵を使って作られた
「その公開鍵が誰のものか」までは、検証しても分かりません。
例えば、攻撃者が、example.com になりすます場合を考えます。
- 攻撃者が、自分用の鍵ペアを作る
- 偽のデータを用意し、攻撃者自身の秘密鍵で署名する
- 「これは example.com の公開鍵です」と偽って、攻撃者自身の公開鍵を受信者に渡す
- 受信者は、渡された公開鍵で署名を検証する
手順4の検証は、数学的には正しく成功します。署名を作った秘密鍵と、検証に使った公開鍵が本当にペアだからです。間違っているのは、受信者が「この公開鍵はexample.comのものだ」と思い込んでいる点です。
改ざんとの違い
| ケース | 検証の結果 | 理由 |
|---|---|---|
| データが途中で書き換えられた | 失敗する | ハッシュ値が一致しなくなる |
| 攻撃者が偽データ・署名・公開鍵をまとめて用意した | 成功してしまう | すべてが整合しているため |
ハッシュ値の照合で検出できるのは、前者の「署名後の改ざん」だけです。後者は、検証の仕組みの外側で、公開鍵と持ち主の対応を保証しなければ防げません。
つまり、公開鍵と持ち主を結びつける仕組みが別に必要になります。この結びつけ方が、次の2つのユースケースで大きく異なります。
ユースケース1: SSHの鍵認証
SSHでは、デジタル署名を使う場面が2つあります。署名する側と検証する側が逆向きで、どちらも「公開鍵と持ち主をどう結びつけるか」が問題になります。
| ユーザ認証 | サーバ認証 | |
|---|---|---|
| 確かめたいこと | 接続してきたのは本人か | 接続先は本物のサーバか |
| 署名する人 | クライアント(ユーザ) | サーバ |
| 検証する人 | サーバ | クライアント |
| 公開鍵の置き場所 | サーバの ~/.ssh/authorized_keys
|
クライアントの ~/.ssh/known_hosts
|
SSHの公式ドキュメントでは、サーバ認証を「ホスト認証」、サーバの鍵を「ホスト鍵(host key)」と呼びます。この記事では、HTTPSと対比しやすいように、以降は「サーバ認証」「サーバの公開鍵」と書きます。
ユーザ認証: authorized_keys
SSHの公開鍵認証では、デジタル署名をユーザ認証に使っています。
- ユーザの公開鍵を、サーバの
~/.ssh/authorized_keysに事前に登録しておく - 接続時に、クライアントがセッション固有のデータに秘密鍵で署名して送る
- サーバは登録済みの公開鍵で署名を検証する
- 検証に成功すれば、「その秘密鍵を持つ本人」と認証される
秘密鍵そのものはネットワークに流れません。署名はそのセッション限りで、使い捨てです。公開鍵の信頼性は、管理者が事前に登録したという人手による信頼で担保されます。
公開鍵認証は、デジタル署名を認証に応用したもの、と考えると整理しやすいです。
サーバ認証: known_hosts
逆向きの問題もあります。接続先のサーバが本物かどうかを、クライアントはどう確かめるのでしょうか。
SSHでは、接続時にサーバが自分の公開鍵をクライアントに提示し、対応する秘密鍵で、鍵交換のデータ(接続ごとに異なる値を含む)に署名します。クライアントはサーバの公開鍵でこの署名を検証し、「いま接続している相手が、この公開鍵の持ち主」であることを確かめます。
ただし、ここで先ほどの問題に戻ります。攻撃者が自分の鍵を提示して署名しても、検証は成功してしまいます。その公開鍵が本物のサーバのものかは、署名の検証だけでは分かりません。
SSHはこの問題に、クライアント側の ~/.ssh/known_hosts で対処します。接続したことのあるサーバの公開鍵を保存しておき、次回以降は保存済みの鍵と照合します。
初回接続: フィンガープリントを人が確認する
初めて接続するサーバの公開鍵は、known_hosts に載っていません。そのため、初回はクライアントが次のような確認を表示します。
The authenticity of host 'example.com (203.0.113.10)' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
フィンガープリントは、サーバの公開鍵のハッシュ値です。公開鍵そのものは長いため、人間が照合しやすい短い値にして表示しています。
ここで yes と答えると、サーバの公開鍵が known_hosts に保存されます。本来は、サーバ管理者から別の経路(社内文書、管理画面、電話など)で受け取ったフィンガープリントと見比べて、人が「この鍵は本物だ」と判断することが期待されています。
yes と答える操作そのものが、本物のサーバであることを保証するわけではありません。初回接続時に攻撃者が介在していれば、攻撃者の公開鍵を登録してしまう可能性があります。
2回目以降: 保存済みの鍵と照合する
2回目以降は、サーバが提示した公開鍵を known_hosts の内容と照合します。一致すれば、そのまま接続できます。一致しない場合は、次のような警告が出て接続が止まります。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
原因としては、サーバの再構築などで鍵が変わった場合のほか、中間者攻撃で別のサーバに誘導されている可能性があります。
TOFU: 最初の1回は信じるしかない
この方式は TOFU(Trust On First Use) と呼ばれます。最初の1回だけは、ユーザが「この鍵を信じる」と決めて受け入れ、2回目以降はその記録との一致で守る、という考え方です。
裏を返すと、初回接続の時点で攻撃者が間に入っていると、偽の公開鍵を信じて登録してしまう弱点があります。実際には、フィンガープリントを確認せずに yes と答えてしまう場面も多く、この弱点が問題になることがあります。
大規模な環境では、TOFUに頼らず、CAがサーバの公開鍵に署名する「SSH証明書」を使う方法もあります。HTTPSの仕組みに近く、後述するPKIの考え方をSSHに持ち込んだものといえます。
ユースケース2: HTTPSサイトへのアクセス
HTTPSでは、ブラウザは接続先について、次の2つの問いに答えを出す必要があります。
- 問い①: 渡された公開鍵は、本当にそのサイト(example.com)のものか?
- 問い②: いま通信している相手は、その公開鍵の持ち主か?
署名はこの2つの問いに1つずつ使われます。誰が署名するかだけでなく、検証で確かめる内容も別です。
| 答える問い | 署名する人 | 署名の対象 | 検証で確かめること | |
|---|---|---|---|---|
| 検証① | 公開鍵は誰のものか | CA(認証局) | サーバ証明書 | この公開鍵は、本当にそのサイトのものか |
| 検証② | 目の前の相手はその公開鍵の持ち主か | Webサーバ | ハンドシェイクのデータ | その公開鍵の持ち主が、いま接続している相手か |
なぜ両方必要なのかは、①②の中身を見たあとで、攻撃のシナリオを使って確認します。
全体像
ブラウザがサイトに接続すると、次の順で2つの署名が検証されます。
以降、①と②を順に見ていきます。
検証①: 公開鍵は誰のものか(CAの署名)
CAとは
ブラウザがサイトから公開鍵を受け取っても、それが本当にそのサイトの鍵なのかは、鍵そのものを見ても分かりません。攻撃者が自分の公開鍵を「example.comの鍵です」と名乗って送ってきても、見分けがつきません。
SSHでは、ユーザ認証なら管理者による事前登録、サーバ認証なら known_hosts への保存(初回は人による確認)で、この問題に対処していました。しかしWebサイトは世界中に無数にあり、ブラウザが全サイトの公開鍵を事前に持っておくことはできません。初めて訪れるサイトの鍵を、毎回ユーザが確認するわけにもいきません。そこで、信頼できる第三者に「この公開鍵はこのドメインのものだ」と保証してもらう仕組みが必要になります。この第三者が 認証局(CA) です。
CAは、「このドメインの持ち主は、この公開鍵を使っている」ことを確認し、証明書に署名して発行する組織です。
サーバ証明書には、次の情報が入っています。
- サーバの公開鍵
- ドメイン名などの識別情報
- 有効期間
- 発行者(どのCAか)
- CAによるデジタル署名
署名するのはCA、署名の対象は証明書の内容(サーバの公開鍵やドメイン名など)です。
ブラウザはどう検証するか
ブラウザは、証明書のCA署名をCAの公開鍵で検証します。検証に成功すれば、「この証明書は正規のCAが発行したもので、書き換えられていない」と分かります。
では、そのCAの公開鍵が本物だとどう保証するのか。ここでも同じ問題が出てきます。答えは、主要なCAのルート証明書(CAの公開鍵)を、OSやブラウザが最初から内蔵していることです。内蔵されたものは、OSやブラウザの提供元が事前に審査して信頼しています。SSHの「管理者による事前登録」に近い位置づけです。
証明書チェーン
実際には、ルートCAが直接サーバ証明書に署名することはあまりありません。万が一、ルートCAの秘密鍵が漏洩すると、そのルートに依存するすべての証明書の信頼が失われるため、インターネットから隔離された専用ハードウェアで厳重に封印されています。そのため、日々の証明書発行は「中間CA」に委譲し、万が一の被害を局所的に抑えられる多層防御の設計になっています。
ルートCAが署名する代わりに、次のような連鎖になります。
ブラウザは、サーバ証明書 → 中間CA → ルートの順にたどり、それぞれの署名を検証します。最後に内蔵のルート証明書までたどり着けば、信頼できると判断します。これが証明書チェーンで、全体の仕組みが PKI(公開鍵基盤) です。
証明書チェーンの検証に成功し、必要な条件も満たしていれば、証明書が正規のCAの発行したものだと分かります。さらにブラウザは、証明書に記載されたドメイン名(SAN)が接続先のホスト名と一致することも確認します。ここまでで、「この公開鍵は、確かにexample.comのものだ」と分かりました。
検証②: 持ち主は今の接続相手か(サーバによる署名)
検証①で分かったのは、あくまで「この公開鍵はexample.comのもの」ということです。しかし、サーバ証明書は誰でも入手できる公開情報です。他人の証明書をそのまま送りつけるだけなら、攻撃者にもできてしまいます。
そこで、TLSハンドシェイクの中で、サーバが自分の秘密鍵で署名します。署名の対象は、その接続でやり取りしたハンドシェイクのデータで、接続ごとに異なる値が含まれます。ブラウザは、証明書に入っているサーバの公開鍵でこの署名を検証します。
- 検証に成功した場合、署名は、証明書の公開鍵に対応する秘密鍵によって行われた。つまり、いま接続している相手が、証明書の本当の持ち主
- 攻撃者は証明書を持っていても秘密鍵を持たないので、この署名を作れない
- 署名は接続ごとの使い捨てなので、過去の署名を使い回すこともできない
これはSSHのユーザ認証・サーバ認証と同じ構造です。SSHではクライアントやサーバが、HTTPSではサーバが、「秘密鍵を持っていること」を署名で示しています。
なぜ①と②の両方が必要か
どちらか一方だけだと、なりすましを許してしまいます。実際に攻撃者の立場で見てみます。
もし②がなかったら(①だけの場合)
サーバ証明書は誰でも入手できる公開情報です。攻撃者は example.com の本物の証明書をそのままコピーして、ブラウザに送りつけます。
証明書は本物なので①は通ります。しかし、攻撃者は証明書に対応する秘密鍵を持っていないため、②の署名が作れず、ここで見破られます。
もし①がなかったら(②だけの場合)
今度は、攻撃者が自分で鍵ペアを作り、自分の公開鍵を入れた「example.comの証明書」を自作します。
秘密鍵と公開鍵は正しいペアなので②は通ります。これは、前半の「偽の公開鍵を渡されるとどうなるか」とまったく同じ構図です。しかし、その証明書にはCAの署名がない(あるいは信頼できるルートまでたどれない)ため、①で見破られます。
HTTPSを使っていても、フィッシング詐欺は防げない
「HTTPSで接続していて、証明書の検証にも成功しているなら、そのサイトは安全なのでは?」と思うかもしれません。
しかし、HTTPSの証明書が証明するのは、基本的には「接続先が証明書に記載されたドメイン名のサイトである」ということです。そのサイトが善良な運営者によって運営されていることや、詐欺を行わないことまで保証するものではありません。
例えば、攻撃者が本物の銀行サイトに似せた偽サイトを用意し、その偽サイト用の正規の証明書を取得したとします。この場合、ブラウザは証明書を正しく検証できるため、HTTPSで安全に通信できます。しかし、接続先は本物の銀行ではなく、攻撃者が用意した偽サイトです。
つまり、HTTPSは通信相手のドメイン名を確認するための仕組みであり、そのサイト自体が信用できるかどうかを保証する仕組みではありません。
フィッシング詐欺を防ぐには、HTTPSの表示だけで安心せず、アクセス先のドメイン名が正しいかどうかも確認することが重要です。
3つの場面の比較
| 比較項目 | SSHのユーザ認証 | SSHのサーバ認証 | HTTPSサイトへのアクセス |
|---|---|---|---|
| 確認できること | ユーザが秘密鍵を持つ本人であること | 接続先が登録済みの(本物の)サーバであること | 接続先が、証明書で確認されたドメイン名に対応する相手であること |
| 署名する人 | クライアント(ユーザ) | サーバ | ①CA、②サーバ |
| 署名の対象 | セッション固有のデータ | 鍵交換のデータ | ①サーバ証明書、②ハンドシェイクのデータ |
| 検証する人 | サーバ | クライアント | クライアント(ブラウザ) |
| 検証に使う公開鍵 | ユーザの公開鍵 | サーバの公開鍵 | ①CAの公開鍵、②サーバの公開鍵 |
| 公開鍵を信じる根拠 | 管理者による事前登録(authorized_keys) | 初回の人による確認と、その記録(known_hosts、TOFU) | ルート証明書からの信頼の連鎖(PKI) |
署名する側と検証する側の向き、そして公開鍵の信頼を担保する方法は、場面ごとに違います。特に、SSHのサーバ認証とHTTPSは、どちらも「接続先のサーバは本物か」を確かめる点で同じですが、信頼の根拠は「最初の1回の確認(TOFU)」と「第三者による保証(PKI)」で対照的です。ただし、どちらも「秘密鍵で署名し、公開鍵で検証する」という同じ仕組みの上に成り立っています。
まとめ
デジタル署名の検証で分かるのは、「この署名は、この公開鍵に対応する秘密鍵を使って作られた」ことだけです。その公開鍵が誰のものかは、検証だけでは分からないため、別の仕組みで公開鍵と持ち主を結びつける必要があります。
この記事で見た3つの場面では、その結びつけ方がそれぞれ異なっていました。
-
SSHのユーザ認証: 管理者が公開鍵を
authorized_keysに事前登録する -
SSHのサーバ認証: 初回に人がフィンガープリントを確認し、
known_hostsに記録する(TOFU) - HTTPS: CAが署名した証明書と、ルート証明書からの信頼の連鎖(PKI)で保証する
どの場面でも、「秘密鍵を持っていること」の証明には署名を使い、「その公開鍵が誰のものか」の保証には別の信頼の仕組みを使っています。