パスワードレス認証の原理:FIDO2 / WebAuthn と公開鍵チャレンジ
偽サイトに入力させても盗めない。公開鍵ペアとチャレンジ署名でフィッシングを原理から無効化する FIDO2/WebAuthn の登録・認証フローと、パスキー同期の設計判断を押さえられます。
- 1.WebAuthn はサイトごとに公開鍵ペアを生成し、サーバーが送るチャレンジへ秘密鍵で署名する方式。共有秘密(パスワード・コード)を一切送らないため、漏れて再利用される素材が存在しない。
- 2.フィッシング耐性の核心はオリジンバインディング。署名対象に「ブラウザが見ている本物のオリジン」が含まれ、認証器はサーバー指定の rpId としか一致させないので、偽ドメインでは署名が原理的に通らない。
- 3.パスキーは同期型(クラウドで複数端末に複製・復旧容易)と端末束縛型(鍵が外に出ない・高保証)に分かれ、AAGUID やアテステーションで「どんな認証器か」を検証できる。
解きたい問題:共有秘密がある限りフィッシングは終わらない
パスワードでも TOTP でも SMS コードでも、本質は同じ「サーバーと利用者が共有する秘密を、利用者がその場で送り返す」モデルです。ここに構造的な弱点があります。送り返す秘密は、本物そっくりの偽サイトに入力させればそのまま盗めるのです。中継型フィッシング(AiTM)を使えば、ワンタイムコードであってもリアルタイムに横取りして即座に本物へ流用できます。多要素認証で要素を増やしても、すべての要素が「入力して送る文字列」である限り、まとめて中継されて突破され得ます。
FIDO2 / WebAuthn が解くのはこの一点です。送り返す秘密そのものを廃止する。代わりに公開鍵暗号のチャレンジレスポンスを使い、しかも「どのサイトに対して署名しているか」を署名そのものへ織り込むことで、偽サイトでは原理的に署名が成立しないようにします。これが「フィッシング耐性」の正体です。
全体像:FIDO2 を構成する2つの仕様
FIDO2 は単一の仕様ではなく、役割の異なる2つが噛み合った総称です。
| 仕様 | 策定主体 | 受け持つ範囲 |
|---|---|---|
| WebAuthn | W3C | ブラウザ(RP側JS)とサーバーの間のAPI。鍵生成・署名を要求し検証する |
| CTAP2 | FIDO Alliance | ブラウザと外部認証器(セキュリティキー・スマホ)の間のプロトコル |
登場人物は3者です。RP(Relying Party、サービス側のサーバーとサイト)、ブラウザ/プラットフォーム(仲介役)、認証器(Authenticator、秘密鍵を保持し署名する主体)。認証器には2種類あり、端末に組み込まれた プラットフォーム認証器(Touch ID、Windows Hello、Android の指紋)と、USB/NFC/BLE で外付けする ローミング認証器(YubiKey 等のセキュリティキー)があります。
ここで決定的に重要なのは、秘密鍵は認証器の外に出ないことと、RP は公開鍵しか保存しないことです。サーバー側のデータベースが丸ごと漏洩しても、そこにあるのは検証用の公開鍵だけ。攻撃者はそれだけでは何の認証も突破できません。これはパスワードの安全な保存で苦労するハッシュ・ソルトの問題系を、そもそも「秘密を預からない」ことで回避する設計です。
登録フロー:サイトごとに鍵ペアを作る
登録(registration、navigator.credentials.create())では、そのサイト専用の鍵ペアを認証器内で新規生成します。流れを原理レベルで追います。
1. RPサーバー → ブラウザ: PublicKeyCredentialCreationOptions
- challenge(毎回ランダムな乱数。リプレイ防止)
- rp.id(= 登録先ドメイン。例 "example.com")
- user.id(このサイトでのユーザー識別子。再利用しない不透明値)
- pubKeyCredParams(許可する署名アルゴリズム。例 ES256, EdDSA)
2. ブラウザ: clientDataJSON を組み立てる
{ type: "webauthn.create",
challenge: <受け取った乱数>,
origin: <ブラウザが実際に見ているオリジン> }
3. 認証器: 鍵ペアを生成し、credentialId と公開鍵を返す
- rpId と紐づけて秘密鍵を内部に保管
- 利用者の存在確認(指紋・PIN 等)を必須にできる
4. RPサーバー: 公開鍵 + credentialId + AAGUID を保存(秘密鍵は受け取らない)
肝は手順2と3です。ブラウザは「自分がいま接続している本物のオリジン」を clientDataJSON に書き込み、認証器はサーバー指定の rp.id を鍵に焼き付けます。利用者は触れたり PIN を入れたりする(user presence / user verification)だけで、秘密鍵に直接触れることはありません。
rp.id は eTLD+1 を基準とした登録ドメイン(例 example.com)で、そのオリジン(https://login.example.com など)はその登録可能サフィックス配下でなければ拒否されます。ブラウザがこの照合を強制するため、RP は「自分のドメインに紐づく鍵」しか作れず、他サイトの鍵を盗み見ることも作らせることもできません。
認証フロー:チャレンジに署名し、オリジンを束ねる
認証(authentication、navigator.credentials.get())では、登録済みの秘密鍵でサーバーのチャレンジに署名します。
1. RPサーバー → ブラウザ: PublicKeyCredentialRequestOptions
- challenge(新しいランダム乱数)
- rpId(= "example.com")
- allowCredentials(任意。使える credentialId のヒント)
2. ブラウザ: clientDataJSON を作る
{ type: "webauthn.get", challenge, origin: <本物のオリジン> }
そのハッシュを clientDataHash として認証器へ渡す
3. 認証器が署名する対象(中身が要点):
signature = Sign(秘密鍵, authenticatorData ‖ SHA-256(clientDataJSON))
authenticatorData の中身:
- rpIdHash = SHA-256(認証器に焼き付けた rpId)
- flags = user present / user verified ビット
- signCount = 署名カウンタ(クローン検出用、単調増加)
4. RPサーバーの検証:
a. clientDataJSON.origin が登録済みオリジンと一致するか
b. clientDataJSON.challenge が自分が送った乱数と一致するか
c. authenticatorData.rpIdHash == SHA-256(自分の rpId) か
d. 公開鍵で signature を検証
e. signCount が前回より増えているか
ここにフィッシング耐性のすべてが詰まっています。署名対象には clientDataHash(その中にブラウザが見た本物のオリジンが入る)と rpIdHash(認証器が知っている正しいドメイン)の両方が含まれます。偽サイト examp1e.com に誘導された場合、ブラウザが書き込むオリジンは examp1e.com になり、サーバー側の検証(a)で弾かれます。さらに偽サイトの rpId は本物の鍵に焼き付けた rpId と異なるため、認証器はそもそも該当する秘密鍵を見つけられません。利用者が騙されても、署名が機械的に成立しないのが本質です。
TOTP のコードは「どのサイトに見せるか」の情報を持たない汎用の数字列なので、偽サイトに入力すれば本物へ中継できます。WebAuthn の署名は「どのオリジン・どの rpId に対するものか」を署名対象に内包するため、宛先がずれた瞬間に検証が落ちます。耐性の差は要素の数ではなく、署名がコンテキストに束縛されているかに由来します。
チャレンジ・カウンタ・存在確認が支える3つの保証
WebAuthn の各要素は別々の脅威を潰します。
- challenge(毎回新しい乱数):過去の署名を録画して使い回すリプレイ攻撃を防ぐ。サーバーは自分が今回出した乱数と一致する署名だけを受理し、消費したチャレンジは破棄する。
- signCount(署名カウンタ):認証器が署名のたびに増やす単調増加値。サーバーが前回値より小さい・等しい値を見たら、秘密鍵が複製されたクローン認証器の疑いを検出できる(ただし同期パスキーではカウンタを維持しない実装もあり、その場合は検出シグナルとして使えない)。
- user verification(UV):指紋・顔・PIN による本人確認ビット。これが立っていれば「鍵を持つ端末を、正しい本人が操作した」ことまで保証され、所持要素と知識/生体要素を1アクションで束ねた多要素になる。UV なしの user presence のみなら「人が触れた」事実だけを示す。
つまり1回の署名で「正しいサイトに対して(オリジン束縛)/使い回しでなく(challenge)/本人が(UV)/複製でない鍵が(signCount)」という複数の主張が同時に検証されます。これが認証という行為で、サイトに入った後の権限判断は別問題です(認証と認可の違いを参照)。
パスキー:同期型と端末束縛型の設計判断
「パスキー(passkey)」は WebAuthn の資格情報を利用者向けに呼び替えた語ですが、運用上は鍵がどこに存在しうるかで2つに大別され、ここがセキュリティ設計の分岐点になります。
| 観点 | 同期パスキー(synced) | 端末束縛パスキー(device-bound) |
|---|---|---|
| 鍵の所在 | クラウド経由で同一ユーザーの複数端末に複製 | 生成した認証器の中だけ。外に出ない |
| 代表例 | iCloudキーチェーン / Googleパスワードマネージャー | YubiKey 等のセキュリティキー |
| 紛失時の復旧 | 別端末で同期され容易 | 予備キー登録が必須。なければ失う |
| 保証の根拠 | プラットフォームのアカウント+暗号化同期 | ハードウェア内の鍵分離 |
| 脅威面 | クラウドアカウント乗っ取りに帰着する | 物理的紛失・盗難に帰着する |
同期パスキーは UX が劇的に良く(機種変で鍵を引き継げる)、コンシューマ向けに普及を後押ししました。ただし鍵の安全性は最終的に同期を担うクラウドアカウントの強度へ移譲されます。一方、端末束縛パスキーは鍵がハードウェアから出ないため最高水準の保証を与えますが、紛失=失効に直結するため**複数登録(予備の認証器)**の運用が前提になります。高保証が要る業務システムでは、後者を必須にしたり、後述のアテステーションで認証器の種類を絞り込んだりします。
パスキーを導入しても、リカバリ経路が「メールに送るマジックリンク」だけなら、攻撃者はそのフィッシングしやすい経路を突きます。強い認証は最弱の登録・回復経路と同じ強さしか持ちません。予備認証器の登録、複数パスキーの強制、回復時の本人確認強化までを一体で設計してください。
アテステーション:その認証器は信頼できるか
登録時、認証器は「自分がどんな製品か」を示す**アテステーション(attestation)**を任意で添付できます。これは認証器の製造元が持つ鍵で署名された証明で、RP は AAGUID(認証器モデルの識別子)と署名チェーンを検証し、「FIPS 認証済みのハードウェアキーだけを許可する」といったポリシーを実装できます。仕組みとしては製造元のルートまで遡って署名を検証する点でデジタル署名スキームと同じ信頼の連鎖です。
ただしアテステーションはプライバシーと表裏一体です。モデルを識別できることは追跡可能性にもつながるため、コンシューマ向けでは多くの場合 attestation: "none"(検証しない)が選ばれます。高保証が要る企業内システムでは認証器を厳格に選別し、一般公開サービスではプライバシーを優先する――この使い分けが実務上の判断軸です。
まとめ
FIDO2 / WebAuthn のフィッシング耐性は「要素を増やす」発想ではなく、送り返す共有秘密を廃し、署名対象にオリジンと rpId を束ねるという構造から生まれます。サーバーは公開鍵しか持たないため漏洩しても再利用できる素材がなく、challenge がリプレイを、signCount がクローンを、user verification が本人性を同時に担保します。パスキーの実装では、同期型(可用性重視)と端末束縛型(保証重視)のトレードオフ、アテステーションによる認証器選別とプライバシーのバランス、そしてリカバリ経路の強度が設計の勘所です。
土台となる公開鍵暗号と署名の基礎は暗号の基礎、署名検証の内部はデジタル署名スキームの内部、認証後の権限判断は認証と認可の違いと併せて押さえると、なぜこの方式が「騙されても破られない」のかが原理から腑に落ちます。
セキュリティ Article
パスワードレス認証の原理:FIDO2 / WebAuthn と公開鍵チャレンジを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
FIDO2
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 5
導入後に効く点
フィッシング耐性の核心はオリジンバインディング。署名対象に「ブラウザが見ている本物のオリジン」が含まれ、認証器はサーバー指定の rpId としか一致させないので、偽ドメインでは署名が原理的に通らない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「FIDO2 / WebAuthn」に近いか確認する。
- 強みである「WebAuthn はサイトごとに公開鍵ペアを生成し、サーバーが送るチャレンジへ秘密鍵で署名する方式。共有秘密(パスワード・コード)を一切送らないため、漏れて再利用される素材が存在しない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。