前回、マイナンバーカードを USB リーダー無し(スマホ NFC)で WebAuthn 認証器にするところまで作りました。ただしテスト相手は webauthn.io という検証用サイト。本当の問いは「実在のサービス、それも厳しめの金融機関で通用するのか?」です。今回、ちょうどパスキーを必須化しつつある楽天証券(自分の口座)で、登録とログインの両方を試しました。結論から言うと——通りました。専用ハード無し・マイナカードの所持と PIN だけで、本番口座にログインできました。
先に明確にしておきます。これは楽天証券の脆弱性の話では一切ありません。自分の口座に、正規の「パスキーを作成」手順で、自作の認証器を1つ追加登録しただけです。むしろ「楽天のパスキー実装が標準に忠実だから、第三者実装の認証器でも受け入れられた」という、健全さの証明でもあります。既存のログイン手段(パスワード等)は残したまま試しています。
まず「相手の要求」を、口座に手を付けず覗く
本番の金融口座で行き当たりばったりに登録を試すのは怖い。そこで拡張機能に recon(偵察)モードを仕込みました。navigator.credentials.create() / get() が呼ばれたら、RP(楽天)が要求してくるオプションをコンソールに出力して、そこで処理を中断する——カードにもサーバにも一切コミットせず、要求だけを読み取る安全弁です。
分かった楽天の要求はこうでした。
- residentKey: required — ユーザー名レス(discoverable credential)必須
- userVerification: required — 生体/PIN による本人検証フラグ必須
- pubKeyCredParams に ES256(-7)あり、EdDSA(-8)は無し
- attestation: “direct” — アテステーション(認証器の素性証明)を要求
ES256 は我々のサイトごとに導出する P-256 鍵でそのまま対応できる。UV フラグも resident key も実装済み。問題は最後の attestation: "direct" でした。
最大の分岐点 — プライバシーを捨てずに通るか
我々の認証器は、プライバシーのためあえて素性を明かしません。アテステーションは fmt: none、AAGUID(認証器の型番 ID)はすべてゼロ。これは「サイトをまたいで同一カードだと名寄せされない」ための設計です(耐タンパー vs プライバシーの記事参照)。
一方、楽天は direct(素性を見せろ)を要求している。ここが正面衝突します。ただし WebAuthn の仕様上、direct はあくまで「要求」であって、認証器は none を返してよく、それを受理するかどうかはサーバ側のポリシー次第。金融機関なら型番のホワイトリスト(FIDO Metadata Service)で弾く可能性も十分あり——これはクライアントからは判別できず、実際に投げてみるしかない領域でした。
登録 — 「パスキーの作成が完了しました」
recon を外し、スマホのリレーを準備して、楽天の「パスキーを作成」を押す。スマホにカードをかざすと、カードの署名を種に P-256 鍵が導出され、fmt: none のアテステーションが組み上がって楽天へ。返ってきたのは:
パスキーの作成が完了しました。
楽天は fmt: none / AAGUID=ゼロ を受理しました。つまり、プライバシー型の認証器を拒まなかった。事前に心配していた「金融機関は型番検証で弾くのでは」は、少なくとも楽天では杞憂でした。プライバシーを一切捨てずに、本番口座へ登録できたわけです。
ログイン — そして、ここで一度つまずいた
残るはログイン。ところが「パスキーでログイン」を押しても、我々の認証器に処理が回ってこない。調べると、楽天は passkey ログインを条件付き UI(autofill 型、mediation: 'conditional')で呼んでいました。我々の拡張は当初、autofill の自動 get を OS 側(Touch ID)に素通しする作りだったため、マイナが出番を得られていなかったのです。
正直に書くと、ここで私は計測ミスで一度「Myna では入っていない」と誤判定しました。リレーは個々のリクエストをログに残さず、拡張も get 成功時にコンソール出力しない——その2つの「無音」を根拠に早合点したのです。本当の証拠はスマホ側の応答ログでした。条件付き UI の get も拾うよう拡張を直し、再挑戦。今度はスマホにこう出ました。
リクエスト受信: sign / rakuten-sec.co.jp
PIN残:3
✅ 応答送信 (sign/es256, pub 64B)
楽天が呼んだ get() を拡張が横取りし、リレー経由でスマホへ。カードで ES256 署名を生成して返すと、楽天が登録済みのマイナ公開鍵で検証し——ログイン成立。会員ページが開きました。専用リーダーもパスキー対応端末も使わず、マイナカードを当てるだけで、実在の証券口座にログインできた瞬間です。
これが意味すること
webauthn.io のデモとは重みが違います。相手はセキュリティ最重要の金融機関、しかもパスキー必須化を進める実サービス。そこで、
- 専用ハード不要:全員が持つスマホの NFC でカードを読む
- 所持 + PIN:マイナカードという強い所持 factor で、パスワードレスにログイン
- プライバシー維持:サイトごとに別鍵、AAGUID ゼロで名寄せ不可、マイナンバーも証明書も漏れない
——が同時に成立した。「全員が既に持っている国民 ID カードを、フィッシング困難な WebAuthn 認証器にする」という当初の構想が、実在の金融サービスで端から端まで動いたことになります。
正直な限界
- 耐タンパー性:鍵の導出・署名はソフト側で行うため、専用キー(YubiKey 等)には及びません。位置づけは「金庫」ではなく「全員のカードで所持+PIN のフィッシング耐性を低コストに足す層」。
- リレーはまだプロト:今回の Wi-Fi リレーは疎通優先で近接性の証明が無い。本命は BLE トランスポート(電波到達=近接=遠隔リレー対策)です。
- 運用上の注意:楽天は条件付き UI でログインを呼ぶため、拡張を有効にしているとマイナ側に処理が向きます。普段の通常ログインを邪魔しないよう、使わない時は拡張をオフに。
限界はありつつも、マイナンバーカードが、実在の金融機関で「使えるパスキー」になったのは大きな一歩でした。次はリレーを BLE 化して近接性を担保し、より実用に近づけていきます。

コメントを残す