これまでの記事で、マイナンバーカードを Safari の WebAuthn 認証器にし、Google でも使えるようにしました。ただ実用には大きな壁が一つ残っていました——カードの読み取りに USB カードリーダーが要ること。会社で全員に使ってもらうのに「各自リーダーを買って」は現実的ではありません。今回はこの「読み取りハード問題」を、スマホの NFC で超えられるか実機で検証しました。
答えは「スマホ」。しかも前例がある
マイナンバーカードは NFC カードです。そしてほぼ全員が NFC 付きスマホを持っている。実際、デジタル庁の「デジタル認証アプリ」も、スマホの NFC でカードを読んでいます。つまり専用リーダーは要らない——理屈ではそうですが、「自分で作った third-party アプリが、マイナの利用者証明用鍵で署名できるのか?」が分かれ目でした。ここが通らないと、スマホを自前の認証器にする構想は成立しません。
Android で最小実装
Android は IsoDep で ISO7816 の APDU を自由に投げられます(third-party に開放)。カードに送るコマンドは、デスクトップ版(jpki/myna)と同じ 5 つだけ:
1. SELECT AP : 00 A4 04 0C 0A D3 92 F0 00 26 01 00 00 00 01 (JPKI AP)
2. SELECT EF : 00 A4 02 0C 02 00 18 (利用者証明用PIN)
3. VERIFY : 00 20 00 80 <len> <PIN(ASCII)> (残回数は 00 20 00 80 → 63Cx)
4. SELECT EF : 00 A4 02 0C 02 00 17 (利用者証明用 秘密鍵)
5. SIGN : 80 2A 00 80 <len> <DigestInfo> 00 (RSA署名 256B)
Kotlin では NFC リーダーモードでタグを掴み、isoDep.transceive() でこの 5 コマンドを順に送るだけ。Compose も外部ライブラリも不要の最小アプリです。
⚠️ 安全策:PIN は3回ミスでロック
利用者証明用パスワードは3回間違えるとロックします。検証アプリでは事故防止に、
- 署名前に残回数をチェック(データ無しの VERIFY →
63 Cxの x が残り回数) - 残回数が3未満なら自動で中止(うっかり連続ミスでのロックを防ぐ)
- VERIFY は1回だけ、失敗したら即停止
自前でマイナの PIN を扱うなら、この残回数ガードはほぼ必須だと思います。
結果:スマホだけでマイナに署名できた
実機(Pixel)で、PIN を入れてカードを背面にかざすと:
PIN残回数: 3
✅ VERIFY 成功
✅ SIGN 成功! RSA署名 256 bytes
seed = SHA256(署名) = 41E9F2AA1DAFBF75…
自作の third-party アプリが、専用リーダー無しで、マイナの利用者証明用鍵による署名を取得できました。この RSA 署名を種(seed)にして、サイトごとの鍵(Ed25519 / ES256)を導出する——という、これまで作ってきた仕組みにそのまま繋がります。
これで何ができるか
- 読み取りハード問題の解消:全員が持つスマホの NFC でカードを読む。専用リーダー不要。
- スマホを認証器に:スマホで鍵導出・署名し、PC ブラウザの WebAuthn へは自前のリレー(QR + 暗号化チャネル)で渡す構成が見えてくる(標準の「スマホを使う」= caBLE は third-party に開かれていないため、ここは自作する)。
- 企業の機密文書管理:たとえば Google Drive の機密フォルダを開く時だけマイナをかざす——を、追加ハード無しで狙える。
- 将来はスマホ用電子証明書(SE 搭載)で、カードのタップすら不要に。
正直な限界
この方式はカードを「種の供給源」として使い、鍵の導出・署名はソフト側で行います(WebAuthn 用の鍵はカード内の鍵そのものではない)。そのため鍵の耐タンパー性は専用セキュリティキー(YubiKey 等)には及びません。位置づけは「金庫」ではなく「全員が持つカードで、所持 + PIN による本人性とフィッシング耐性を低コストで足す層」。重要操作はさらに OIDC(デジタル認証アプリ)で本人性を再確認する、という多層で補います。
次は、このスマホ署名からサイト別鍵の導出とPC ブラウザへのリレーを実装して、「リーダーレスでの WebAuthn 認証」を通すところまで進めます。

コメントを残す