コンテンツへスキップ

パスワード認証よりSSH鍵認証が推奨される理由 — 何が「証明」されているのか

SSHでサーバーに接続する際、「パスワードより鍵認証のほうが安全」という助言はよく耳にします。では、鍵認証は具体的に何を証明していて、パスワード認証と比べて何が違うのでしょうか。本稿では、両者が「本人であること」をどう証明しているのかという仕組みのレベルから整理します。

認証とは「何かを証明する行為」である

補足: 認証(authentication)とは、接続してきた相手が「本人である」ということを、何らかの根拠をもって確認する手続きのこと。よく似た言葉に認可(authorization)があるが、こちらは「本人だと確認できた相手に、何を許可するか」を扱う別の概念。

認証の方式は突き詰めると、「何を根拠に本人だと判定するか」の違いに帰着します。パスワード認証と公開鍵認証は、この根拠がまったく異なります。

パスワード認証 — 「知っていること」を、知っていると申告して証明する

パスワード認証の構造はシンプルです。クライアントとサーバーの双方が同じ秘密(パスワード)を知っている前提があり、クライアントはその秘密をサーバーに送信し、サーバーは受け取った値を(多くの場合ハッシュ化した上で)手元の記録と照合します。一致すれば本人だとみなす、という仕組みです。

この方式の弱点は、「秘密そのものを、通信の中で申告する」という構造そのものにあります。SSHの通信路自体は暗号化されているため盗聴には強いのですが、それとは別の弱点が残ります。

  • 人間が記憶できるパスワードは、機械的に生成されるランダム値と比べてエントロピー(推測されにくさ)が低くなりがちで、辞書攻撃・総当たり攻撃の対象になりやすい
  • 同じパスワードを複数サービスで使い回している場合、どこか1箇所の漏洩が他のサーバーへの侵入口になる(クレデンシャルスタッフィング)
  • サーバー側が保存している値(ハッシュ)が万が一漏洩すると、オフラインでの総当たり解読を許してしまう

つまりパスワード認証は、「秘密を知っていることを、秘密そのもの(あるいはそのハッシュ)のやり取りによって証明する」方式です。

公開鍵認証 — 「持っていること」を、秘密を渡さずに証明する

公開鍵認証はこれとまったく異なる証明の仕方をします。土台にあるのは非対称鍵暗号(公開鍵と秘密鍵のペア)です。

補足: 非対称鍵暗号とは、暗号化・署名に使う鍵(秘密鍵)と、検証に使う鍵(公開鍵)が別々のペアになっている暗号方式のこと。片方の鍵から、対になるもう片方の鍵を計算で求めることは事実上不可能に設計されている。

サーバー側にあらかじめ登録するのは公開鍵だけです。秘密鍵はクライアント側の手元から一度も外に出ません。認証の際にサーバーはランダムな値(チャレンジ)をクライアントに送り、クライアントはその値に秘密鍵で署名を作って返します。サーバーは登録済みの公開鍵を使ってその署名を検証し、正しく署名できているなら「対応する秘密鍵を持っている」と判定します。

ここが決定的な違いです。パスワード認証では秘密そのもの(パスワード)が通信の中でやり取りされますが、公開鍵認証では秘密鍵は一度も通信路に乗りません。やり取りされるのは「秘密鍵を持っている証拠(署名)」だけで、その証拠から秘密鍵そのものを逆算することは、非対称鍵暗号の設計上できません。

サーバー側の情報が漏れたときの違い

この設計の違いは、「サーバー側が保持している認証情報が漏洩した場合」を考えると際立ちます。

パスワード認証の場合、サーバーが保持しているのはパスワードのハッシュ値です。このハッシュが漏洩すると、攻撃者はオフラインで総当たり攻撃を仕掛けられます。弱いパスワードであれば、時間をかければ解読される可能性があります。

一方、公開鍵認証でサーバーが保持しているのは公開鍵そのものです。公開鍵は名前の通り公開してよい情報で、これが漏洩しても何の被害にもなりません。攻撃に使える秘密は最初からサーバー側に存在しないためです。「漏れて困る情報をサーバー側に置かない」という設計そのものが、公開鍵認証の安全性の核にあります。

実装例 — 明示した鍵1本だけに認証範囲を絞る

このアプリのSSH接続処理(core/ssh_utils.py::get_ssh_connection())では、公開鍵認証を使うだけでなく、認証で試行する鍵の範囲そのものを明示的に絞り込んでいます。

connect_kwargs = {'look_for_keys': False, 'allow_agent': False}
if 'ssh_key_path' in site and site['ssh_key_path']:
    ...
    pkey = load_any_ssh_key(key_path, passphrase=ssh_passphrase)
    connect_kwargs['pkey'] = pkey

return Connection(
    host=site['ssh_host'],
    user=site['ssh_user'],
    port=site.get('ssh_port', 22),
    connect_timeout=15,
    connect_kwargs=connect_kwargs
)

look_for_keys=Falseallow_agent=False は、SSHクライアントライブラリ(paramiko)が既定で行う「~/.ssh/ 配下の鍵を片っ端から試す」「SSHエージェントに登録済みの鍵を試す」という自動探索を止める指定です。その上で pkey にサイト設定で指定した鍵1本だけを明示的に渡しています。

これは単なる好みの問題ではありません。複数サイトを管理する環境で自動探索を許すと、1回の接続の中で複数の鍵候補が次々と試行されることになり、OpenSSHの MaxAuthTries(既定値6)のような認証試行回数の上限にあっさり達してしまいます。サーバー側に fail2ban や OpenSSH の PerSourcePenalties のような接続元IPを一時ブロックする保護機構が入っていると、正規の管理者自身が誤ってブロックされる事態を招きかねません。「この接続で使う鍵はこれ1本」と明示することは、認証の安全性そのものというより、多サイト管理という運用形態に特有の落とし穴を構造的に避けるための設計です。

サーバー側でパスワード認証自体を無効化する

クライアント側で鍵認証を使うだけでなく、サーバー側の sshd_configPasswordAuthentication no を設定し、パスワードでのログイン試行そのものを受け付けない構成にするのも広く知られた定石です。これは公開されているOpenSSHの標準的な設定項目で、特別な運用ノウハウというより、「弱い方の認証経路を、そもそも存在しない状態にする」という単純な原則に基づいています。鍵がどれだけ堅牢でも、同じアカウントにパスワードでもログインできる経路が残っていれば、攻撃対象はそちらに向かうためです。

SSH鍵の種類選びとの関係

以前の記事 SSH鍵の種類(RSA / ED25519 / ECDSA)はどう違い、どれを選ぶべきか では、公開鍵認証の中でどの鍵アルゴリズムを選ぶべきかを扱いました。本稿で扱ったのは一段階手前の話で、「パスワードではなく公開鍵という仕組みそのものを選ぶ理由」です。鍵の種類を選ぶ判断は、まず公開鍵認証という土俵に乗った後に出てくる、次の階層の判断だと位置付けられます。

まとめ

パスワード認証は「秘密を知っていることを、秘密そのもののやり取りで証明する」方式であるのに対し、公開鍵認証は「秘密(秘密鍵)を一度も渡さずに、それを持っている証拠だけを提示して証明する」方式です。この違いは、サーバー側の情報が漏洩した場合の被害の大きさに直結します。パスワードのハッシュは漏洩すれば解読の糸口になりますが、公開鍵は最初から公開情報であり、漏れても攻撃の足がかりにはなりません。「サーバー側に、漏れて困るものを置かない」という設計原則が、SSH鍵認証がパスワード認証より推奨される最も根本的な理由です。