ハードウェアセキュリティキーの内部(CTAP・常駐鍵)
USBキーを挿してタッチするだけの裏側がわかる。ブラウザと認証器をつなぐ CTAP2、ユーザー名を打たずに済む常駐鍵、PINや生体によるユーザー検証、カウンタでクローンを暴く仕組みまで原理から理解できます。
- 1.CTAP2 はブラウザ/OS と外付け認証器(セキュリティキー)の間のプロトコルで、CBOR でエンコードしたコマンドを USB-HID・NFC・BLE 上でやり取りする。WebAuthn が Web 側、CTAP2 が認証器側を担い、両者で FIDO2 を構成する。
- 2.常駐(discoverable)鍵は credentialId と user 情報をキー内部に保存するためユーザー名入力なしでログインできる一方、保存数に上限がある。非常駐鍵は鍵をサーバー側 credentialId に暗号化して外出しするので無制限に作れるが、必ずユーザー識別子の提示が要る。
- 3.署名カウンタは認証のたびキー内部で単調増加し、サーバーが前回値以下を見たら秘密鍵が複製されたクローンを検出できる。ユーザー検証(PIN・指紋)は PIN/UV 認証プロトコルでブラウザと認証器の間を保護し、所持+知識/生体を1タッチで束ねる。
解きたい問題:ブラウザと「鍵を握る箱」をどうつなぐか
FIDO2 / WebAuthn は、サイトごとに公開鍵ペアを作り、サーバーのチャレンジに秘密鍵で署名することでフィッシングを原理から無効化します。ただし WebAuthn 仕様が定めるのは、あくまで ブラウザ(RP側 JavaScript)とサーバーの間の API だけです。秘密鍵を実際に保持し署名を生成する主体が USB に挿した YubiKey のような外付けハードウェアだった場合、その箱とブラウザはどう会話するのか。ここを埋めるのが CTAP2(Client to Authenticator Protocol v2) です。
CTAP2 が解くのは「秘密鍵を絶対に外へ出さない箱に対し、どう鍵生成と署名を依頼し、どう本人確認を伝え、どう結果を受け取るか」という一点です。プラットフォーム内蔵認証器(Touch ID 等)なら CTAP は OS 内部で完結しますが、ローミング認証器を理解するには CTAP の中身を直接見る必要があります。
CTAP2 の通信モデル:CBOR コマンドとトランスポート
CTAP2 では、ブラウザ側(CTAP用語で client)が認証器に対して コマンド を送り、認証器が レスポンス を返します。メッセージは JSON ではなく CBOR(Concise Binary Object Representation)でエンコードされます。バイナリでコンパクトかつパース実装が小さく済むため、メモリの限られたキー内部のファームウェアに向くからです。
| コマンド | 役割 |
|---|---|
| authenticatorMakeCredential | 鍵ペアを生成し公開鍵とアテステーションを返す(= 登録) |
| authenticatorGetAssertion | 登録済み秘密鍵でチャレンジに署名する(= 認証) |
| authenticatorGetInfo | 対応機能・常駐鍵対応・対応PINプロトコル等を申告する |
| authenticatorClientPIN | PIN設定・PIN/UVトークン取得などUV関連の処理 |
これらのコマンドは複数のトランスポート上を流れます。USB-HID(FIDO 専用の HID 利用方法を定めた CTAPHID フレーミング)、NFC、BLE です。どのトランスポートでも CBOR の中身は同じで、違いは下回りのフレーミングだけです。つまり CTAP2 は「物理層に依存しないコマンドセット」として設計されています。
ブラウザは navigator.credentials.create() を受け取ると、clientDataJSON を組み立てて SHA-256 でハッシュし、その clientDataHash と rp.id、許可アルゴリズムなどを CTAP2 の authenticatorMakeCredential パラメータに詰めてキーへ渡します。認証器はオリジン文字列そのものを見ず、ハッシュだけを署名対象に取り込みます。「本物のオリジンを書き込む」役目はブラウザ側にあり、認証器はそれを検証せず束ねる——この分業が CTAP の要点です。
常駐鍵と非常駐鍵:鍵をどこに置くか
CTAP2 最大の設計分岐が、生成した秘密鍵を どこに保存するか です。
非常駐鍵(non-resident、別名サーバーサイド鍵)では、認証器は鍵ペアを生成した後、秘密鍵そのものを認証器内のマスター鍵で暗号化し、その暗号文を credentialId に詰めてサーバーへ返します。認証器は何も覚えません。後日の認証時、サーバーが allowCredentials でその credentialId を送り返すと、認証器はそれを自分のマスター鍵で復号して秘密鍵を一時的に復元し、署名してから捨てます。鍵を外出しするため、認証器の保存容量と無関係に何個でも作れます。これを 鍵ラッピング(key wrapping) と呼びます。
常駐鍵(resident key、現行仕様では discoverable credential と呼ぶ)では、認証器が credentialId・秘密鍵・user 情報(user.id や表示名)を自分の不揮発メモリ内に保存 します。サーバーは credentialId を覚えておく必要がなく、認証要求時に allowCredentials を空にできます。すると認証器は「この rpId に対して自分が保持する鍵」を列挙でき、ユーザーは ユーザー名を一切打たずに ログインできます(ユーザー名なし認証 / username-less)。これがパスキーの「アカウント選択画面が出る」体験の正体です。
| 観点 | 非常駐鍵 (server-side) | 常駐鍵 (discoverable) |
|---|---|---|
| 鍵の保存先 | 暗号化して credentialId に入れサーバーへ | 認証器の不揮発メモリ内 |
| 作成可能数 | 実質無制限(外出しのため) | 上限あり(容量に依存、例 25個前後) |
| ユーザー名入力 | 必須(credentialId を引くために要る) | 不要(キーが user 情報を保持) |
| allowCredentials | サーバーが credentialId を提示する | 空でよい |
| 主な用途 | 二要素目としての追加(既存ID+キー) | パスワードレスの一次認証 |
常駐鍵はキー内部のスロットを消費するため、安価なキーでは数十個で枯渇します。さらに常駐鍵は「物理的にそのキーを持つ者がアカウントを列挙・操作しうる」状態なので、紛失時のリスクが非常駐鍵より高い。だからこそ常駐鍵では PIN などのユーザー検証を必須化し、鍵を消すには PIN/UV トークンを要求する設計になっています。
ユーザー検証:PIN と生体を CTAP はどう運ぶか
CTAP2 は2段階の本人性を区別します。user presence(UP、ユーザーの存在) は「人がキーに触れた」事実だけを示すボタンタッチで、user verification(UV、ユーザー検証) は「正しい本人である」ことを PIN・指紋・PIN コード等で確認したものです。署名内の flags にそれぞれのビットが立ち、サーバーは UV が必要なら立っているかを確認します。
問題は、外付けキーには鍵盤も画面もないことが多い点です。PIN はブラウザ(client)側で入力させ、その PIN を安全にキーへ届けねばなりません。生の PIN を USB 上に流せば盗聴・リプレイの的になります。そこで CTAP2 は PIN/UV Auth Protocol を定めます。
PIN/UV Auth Protocol(概略)
1. client が authenticatorGetInfo でキーの公開鍵共有用パラメータを取得
2. client とキーが ECDH 鍵共有で共有秘密を確立(PIN を平文で流さない)
3. client は入力された PIN を共有秘密由来の鍵で暗号化してキーへ送る
4. キーは PIN を検証し、成功したら「pinUvAuthToken」を発行
5. 以後のコマンドは、このトークンで計算した HMAC(pinUvAuthParam)を添える
→ トークンはセッション限定・リトライ回数制限つき
要点は3つです。第一に PIN は ECDH 鍵共有で確立した共有秘密で暗号化されるため、USB バス上に平文 PIN は流れません。第二に、PIN 検証は キー内部 で行われ、PIN そのものはキーから出ません。第三に、PIN の連続失敗にはリトライ上限があり、規定回数(典型的には8回)を超えるとキーがロックまたは初期化され、総当たりを物理的に封じます。生体認証(指紋センサ内蔵キー)の場合は、指紋テンプレートの照合もキー内部で完結し、内蔵ユーザー検証(built-in user verification)として UV ビットを立てます。
RP が WebAuthn 側で userVerification: "required" を指定すると、CTAP2 は UV 付きのアサーションを要求します。これにより「キーを所持していること(所持要素)」と「PIN を知っている/指紋が一致する(知識・生体要素)」が単一の署名で同時に証明され、多要素認証が1アクションに畳み込まれます。要素を別々に入力させる従来 MFA との体験差はここから来ます。
署名カウンタ:クローンを暴く単調増加値
各認証器は鍵(または全体)ごとに 署名カウンタ(signature counter / signCount) を持ち、authenticatorGetAssertion を実行するたびにこれを増やして authenticatorData に含めます。サーバーは登録時・認証時のカウンタ値を保存し、毎回「前回保存した値より大きいか」を検証 します。
なぜこれでクローンが暴けるのか。攻撃者が物理的に秘密鍵を吸い出して別の偽キーを作ったとします。本物と偽物は独立にカウンタを進めるため、両者を交互に使うと、ある認証でサーバーが受け取るカウンタ値が前回値より小さい、または等しい逆転が起きます。サーバーはこの逆転を「鍵が複製された強いシグナル」として検出し、当該資格情報を失効させられます。秘密鍵が箱から出ない前提が崩れたことを、外側から間接的に観測する仕掛けです。
正常: reg=5 → auth1=6 → auth2=7 → auth3=8 (常に増加、OK)
クローン: 本物=10 で認証 → 偽キー=8 で認証
サーバーは 10 の次に 8 を受信 → 逆転を検出 → 不正の疑い
カウンタは「鍵が1つの箱に閉じている」ことが前提です。iCloud キーチェーン等の同期パスキーでは同じ鍵が複数端末に正規に複製されるため、カウンタを 0 固定にする実装が一般的です。つまり 0 が来たらクローン検出に使わないのが正しい挙動で、サーバーは「カウンタが 0 なら検査をスキップ」する必要があります。これを知らずに厳格比較すると、正規の同期パスキーを誤って失効させます。カウンタはあくまで専用ハードウェアキー向けの補助シグナルです。
まとめ
CTAP2 は、秘密鍵を外へ出さないハードウェアキーへ、ブラウザが CBOR コマンドで鍵生成・署名・本人確認を依頼するためのプロトコルです。鍵の置き場所には2方式があり、非常駐鍵 は秘密鍵を暗号化して credentialId に外出しするので無制限に作れるがユーザー名提示が要り、常駐(discoverable)鍵 はキー内部に user 情報ごと保存するためユーザー名なしログインを可能にする代わりに容量上限を持ちます。PIN/UV Auth Protocol は ECDH 共有秘密で PIN を保護し、検証とリトライ制限をキー内部で完結させて総当たりを封じ、UV ビット1つで所持+知識/生体を束ねます。署名カウンタ の単調増加は、専用キーにおいて秘密鍵の複製を逆転検出する補助手段ですが、同期パスキーでは 0 固定が正となります。
土台の公開鍵チャレンジはFIDO2 / WebAuthn の原理、認証器の信頼検証はパスキーと Attestation、署名検証の内部はデジタル署名スキームの内部と併せて押さえると、1回のタッチに畳まれた保証の層が原理から見通せます。
セキュリティ Article
ハードウェアセキュリティキーの内部(CTAP・常駐鍵)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
FIDO2
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 5
導入後に効く点
常駐(discoverable)鍵は credentialId と user 情報をキー内部に保存するためユーザー名入力なしでログインできる一方、保存数に上限がある。非常駐鍵は鍵をサーバー側 credentialId に暗号化して外出しするので無制限に作れるが、必ずユーザー識別子の提示が要る。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「FIDO2 / CTAP2」に近いか確認する。
- 強みである「CTAP2 はブラウザ/OS と外付け認証器(セキュリティキー)の間のプロトコルで、CBOR でエンコードしたコマンドを USB-HID・NFC・BLE 上でやり取りする。WebAuthn が Web 側、CTAP2 が認証器側を担い、両者で FIDO2 を構成する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。