iOSアプリでマイナンバーカードを扱う 〜リリースまでのポイントを添えて〜

本稿はJCB Advent Calendar 2025の12月19日の記事です。

こんにちは。株式会社ジェーシービーの鈴木です。MyJCBのiOSアプリ開発を担当しています。

先日、MyJCBのiOSアプリにおいて、iPhone内に保存されたマイナンバーカード情報を用いた本人確認機能をリリースしました。

JCBには、最短5分程度で審査が完了し、カードを受け取る前に、ネットショッピングや店頭で利用できる モバイル即時入会サービス(モバ即) があります。

モバ即を利用して入会したユーザが初回ログインをした際、Apple Walletに登録済のマイナンバーカードを用いて本人確認を行うことで、よりスムーズに各種サービスを利用開始できるようになりました。

詳細は下記のプレスリリースをご参照ください。
MyJCB アプリの本人確認で「iPhone のマイナンバーカード」が利用可能

MyJCBアプリでの動作イメージ

さて、iOS 18.5のリリースに伴い、Apple Walletにマイナンバーカードを登録できるようになりました。米国では2022年から一部の州にて運転免許証をApple Walletに登録することができましたが、日本でもマイナンバーカードが対応した形です。

Apple Walletに保存されたマイナンバーカードは、Walletアプリから情報を確認したりカードリーダーを用いて情報を読み取るだけでなく、SDKを使ってアプリから情報を取得することも可能です。
今回、MyJCBアプリで対応した際も、このような方法でApple Wallet内のマイナンバーカード情報を取得しています。

※iPhoneのマイナンバーカードの詳細およびAppleウォレットに追加する方法の詳細は、Appleのホームページおよび デジタル庁のホームページをご覧ください。


それでは、iOSアプリでマイナンバーカードを扱うにあたっての技術的なポイントや実装、リリースに関する注意点について解説していきます。
大枠を理解するため、まずはApple Walletのマイナンバーカード機能がどのような規格に基づいているかを説明します。

Apple Walletのマイナンバーカードで利用されている規格

Apple Walletの身分証明書機能は、Appleの独自規格ではなく、国際標準規格に準拠して実装されています。具体的には ISO/IEC 18013-5ISO/IEC 23220 の2つです。

ISO/IEC 18013-5 (mdoc/mDL)

mdoc (Mobile Document)と呼ばれる、デジタル身分証明書の技術方式に則ったモバイル運転免許証(mDL: mobile Driver’s License)の規格です。
iOSアプリでマイナンバーカード情報を扱うにあたり、データモデルや復号化・検証の内容を定めているため、内容を理解することが重要です。

物理的な運転免許証に代わり、安全で便利に使用するために、例えば情報交換におけるプロトコルや「必要なデータのみを共有する」「ユーザのデータが保存されたかどうかがわかる」「完全性の検証」等の要件が定められています。
マイナンバーカードのようなデジタル身分証明書情報は、この規格のデータモデルに従ってApple Wallet内に格納されています。
データフォーマットには、JSONよりもデータサイズが小さく解析が高速なバイナリ形式である CBOR (Concise Binary Object Representation) が採用されています。

ISO/IEC 23220

政府や民間等が発行するモバイル身分証明書の相互運用を可能にすることを目的とした規定群です。

公式ドキュメントであるApple Developerサイトには実装ガイドが存在しますが、これらの内容も上記2つの規格に基づいて記載されており、設計・実装をするにあたっては、これらの規格を理解しておくとよいでしょう。


続いて、各実装を簡単に説明していきます。

なお、後述しますが、MyJCBアプリでは、最初にPoCを行い一通りの実装を行った後、最終的には認定済みプラットフォーム事業者のサービスを利用してリリースしました。
今回はPoC段階で行っていた実装をメインにご紹介します。

おおまかには「Apple Walletから暗号化済マイナンバーカード情報(CBOR)を取り出し」→「サーバで復号化・検証」の流れになります。

マイナンバーカード情報を読み取るまでの流れ

iOSアプリ側の実装

まずは、iOSアプリからApple Wallet内のマイナンバーカード情報を取得する実装を解説します。

MyJCBアプリでは、メインの画面はUIKitで作成されており、今回はUIKitベースのサンプルコードを紹介します。SwiftUIの場合も、基本的な考え方は同じです。
PassKitフレームワークをインポートし、Wallet APIを利用して実装が可能です。

必要なEntitlementとMerchant ID

マイナンバーカードを扱うにあたって、利用するEntitlementは下記の2点です。

  • com.apple.developer.in-app-identity-presentment
    アプリ内で身分証明書を要求するための権限です。
  • com.apple.developer.in-app-identity-presentment.merchant-identifiers
    Merchant IDの情報を保持するための権限です。
    Apple Payと同様に、Merchant IDの作成が必要です。これは、後述するサーバでの復号処理において、ペアとなる秘密鍵を作成する際にも必要です。

これらのEntilementsを有効にするには、Appleへの申請が必要です。詳細は下記の公式ドキュメントをご参照ください。
Get started with the Verify with Wallet API - Apple Developer

なお、 エミュレータで実施する際はEntitlementsの申請は不要です。
また、エミュレータにはテスト用の米国版運転免許証がインストールされており、カードの準備不要で動作確認をすることができます。
下記はエミュレータで実施する前提で記載します。

サンプルコード解説

ボタンをタップするとApple Walletから「氏名」「生年月日」を取得し、サーバへ送信する主要部分の実装を解説します。
あらかじめ、PassKitをimportしておきます。

.entitlementsへの追加

.entitlementsファイルに、以下の内容を追加します。

"document-types" は本来であればマイナンバーカードを示す "jp-national-id-card" を指定しますが、今回はエミュレータで動作確認を行うため、 "us-drivers-license" を指定しています。
なお、jp-national-id-card"を指定した場合、エミュレータにはマイナンバーカード情報が存在しないためか、動作しませんでした。

"elements" には、取得したいデータ項目を指定します。今回は「氏名(given-name、family-name)」「生年月日(date-of-birth)」を指定しています。

 <key>com.apple.developer.in-app-identity-presentment</key>
    <dict>
        <key>document-types</key>
        <array>
            <string>us-drivers-license</string>
        </array>
        <key>elements</key>
        <array>
            <string>given-name</string>
            <string>family-name</string>
            <string>date-of-birth</string>
        </array>
    </dict>
    <key>com.apple.developer.in-app-identity-presentment.merchant-identifiers</key>
    <array>
        <string>temp.test.merchantid</string>
    </array>

Info.plistへの追加

Info.plistに、以下の内容を追加します。
NSIdentityUsageDescriptionをキーに、マイナンバーカード情報を取得する目的を説明する文字列を設定します。
ここで設定した文字列はApple Walletの生体認証モーダルに表示されます。

 <key>NSIdentityUsageDescription</key>
    <string>for Testing Wallet API</string>

Verify with Wallet専用のボタン作成

PKIdentityButtonを使用して、Verify with Wallet専用のボタンを作成します。スタイルはいくつか選択可能です。今回は ".blackOutline" を使用しています。
(仮実装のため、下記のイメージです)

    private lazy var verifyButton: PKIdentityButton = {
        let button = PKIdentityButton(label: .verify, style: .blackOutline)
        button.addTarget(self, action: #selector(onTapVerifyButton), for: .touchUpInside)
        return button
    }()

PKIdentityNationalIDCardDescriptorの作成

PKIdentityNationalIDCardDescriptorを作成します。ここに、取得したいデータ項目(氏名、生年月日など)を定義します。取得した情報をサーバー等に保存するかどうかも指定します。
ここで指定した内容はApple Walletの生体認証モーダルに表示されます。

    // 取得するデータを定義します
    private func createDescriptor() -> PKIdentityNationalIDCardDescriptor {
        let descriptor = PKIdentityNationalIDCardDescriptor()
        descriptor.region = .japan // 地域を日本に設定する必要があります
        
        descriptor.addElements([.givenName, .familyName, .dateOfBirth], intentToStore: .mayStore(days: 90))
        return descriptor
    }

PKIdentityRequestの作成

PKIdentityRequestを作成します。ここで、先ほど作成したdescriptorをセットします。また、Apple Developerで登録したMerchant IDを指定し、nonceをセットします。

    private func createRequest() -> PKIdentityRequest {
        let request = PKIdentityRequest()
        request.descriptor = createDescriptor()
        // Apple Developerで登録したMerchant IDを指定します
        request.merchantIdentifier = "test.merchantid" 
        
        // Nonceを生成します
        // 今回はテスト用実装のためCryptoKitを使用して生成していますが、
        // 実際にはサーバー側で生成したランダム値を取得してセットすることを推奨します
        request.nonce = Data(AES.GCM.Nonce()) 
        return request
    }

画面読み込み時の処理の実装(リクエスト可能かの判定処理)

PKIdentityAuthorizationControllerを使用して、マイナンバーカードがApple Walletに存在するか確認します。
マイナンバーカードが未登録な場合だけでなく、無効化されている状態でもfalseが返ります。

    var controller = PKIdentityAuthorizationController()
    controller.checkCanRequestDocument(descriptor) { [weak self] canRequest in
        guard let self = self, canRequest else { return }
        // ボタンを画面に追加する処理(省略)
    }

Verify with Walletボタンタップ時の処理

requestDocument()メソッドでApple Walletモーダルを呼び出します。生体認証に成功するとCBORデータが返却されるので、これをサーバ側に送ります。
リクエストに含めるため、今回はCBORやnonceをさらにBase64URLエンコードしています。
Walletモーダルを閉じたりした場合などの失敗時はcatch句で処理します。

    do {
        let request = createRequest()
        
        // ユーザが生体認証で承認するとデータが返却されます
        let document = try await controller.requestDocument(request)

        // CBORデータをBase64URLエンコードします
        let docBase64 = document.encryptedData.base64URLEncodedString()
        // nonceもBase64URLエンコードします
        let nonceBase64 = request.nonce?.base64URLEncodedString() ?? ""

        print("Nonce: \(nonceBase64)")
        print("Encrypted Data: \(docBase64)")
        
        // 暗号化データをサーバに送信(省略)
        print("Encrypted Data Size: \(docBase64.count)")

    } catch {
        // requestDocument()による失敗時の処理(省略)
    }

上記を実装すると、下記の「次で確認: Apple ウォレット」をタップするとApple Walletモーダルが表示され、認証完了後、CBORを取得できます。
下記のサンプルでは実際にサーバ側で復号化する処理を行った上で、画面上に表示しています。

動作イメージ(動画)

動作イメージ

※画面に表示されている情報はiOSエミュレータにプリインストールされているダミーデータです


サーバ側の実装

それでは、サーバ側の実装はどうなるでしょうか。

iOSアプリ側でCBORデータを取得することは難しくありませんが、 復号化・検証にはISO/IEC 18013-5の規格をベースに設計・実装する必要があり、苦労しました。 下記では処理の概要のみご紹介します。
実際にどういったものを実装すべきかをつかむには、下記のドキュメントを参照することをおすすめします。
Verifying Wallet identity requests | Apple Developer Documentation

なお、詳細な実装内容はISO/IEC 18013-5に記載されております。
こちらは有料となりますが、 設計にあたって非常に参考になりますので購入することを強くおすすめします。
ISO/IEC 18013-5:2021 個人識別-ISO準拠の運転免許証-第5部:モバイル運転免許証(mDL)アプリケーション | 日本規格協会 JSA Group Webdesk

鍵情報の読み取り

Apple Developerで作成した、復号化のための秘密鍵を読み取ります。
MyJCBアプリのサーバを配置しているJDEPでは、Google Cloudを利用しているため、鍵管理にはSecret Managerを使用しました。

復号処理

ISO/IEC 18013-5に基づき、以下のような流れで復号を行います。
※あらかじめレスポンスのBase64URLによるデコードを行っておきます

  1. HPKEエンベロープの解析
    HPKEのパラメータ(pkEm: 一時的な送信者公開鍵など)を取り出します。
  2. Session Transcriptの構築
    「Session Transcript」と呼ばれるCBOR構造体を作成します。これにnonceやMerchant ID, Apple DeveloperのTeam ID, 公開鍵情報等を含めます
  3. HPKE復号
    サーバの秘密鍵(受信者秘密鍵)と、再構築したSession Transcriptを使用して、データを復号します。
  4. CBORデコード
    復号に成功するとCBORが得られるため、これをデコードしてJSONライクな構造体に変換し、内容を確認します。

真正性の検証

復号できただけでは不十分で、ISO/IEC 18013-5では、データが正当なものであるかを検証する必要があります。

  1. 発行者署名の検証
    データに含まれる電子署名(Mdoc Issuer Signature)を検証します。デジタル庁(発行者)のルート証明書(IACA証明書)を使用し、信頼できる発行元によって署名されていることを確認します。
  2. デバイス署名の検証
    リクエストを行った正当なiPhoneから送信されたものであるかを検証します。

どうやってテストするのか

「iOSシミュレータを使う」「実機を利用しマイナンバーカードを利用する」の2つの方法に分かれます。

iOSシミュレータを使用する場合

前述の通り、シミュレータにはあらかじめ、ダミーの身分証明書がインストールされています。よって、iOSアプリ側の実装が正しければCBORのモックレスポンスが返却されます。
ただし、これには本物の「発行者署名」は含まれていないため、バックエンドの署名検証ロジックを完全にテストすることはできません。

マイナンバーカード関連の検証環境を使用する場合

バックエンドを含めた完全な結合テストを行うには、デジタル庁が提供する検証環境を利用します。
利用にはデジタル庁へ検証環境アクセス申請を行う必要があります。
詳細はデジタル庁のホームページを参照してください。
(実際の開発には、着手前にまずは「初回申込」を行う形になります)

カード代替電磁的記録(属性証明機能)|デジタル庁


リリースにあたって考慮すべき点

技術的に実装が可能であっても、実際にサービスをリリースする上では、別のハードルがあります。

認定の取得

Apple Walletに導入したマイナンバーカードを用いて、犯収法に基づく本人確認を行う場合、 その検証を行うプログラム(バックエンドシステム)は、内閣総理大臣および総務大臣の認定を受ける必要があります。

自社でゼロから検証システムを開発し、この認定を取得するには、セキュリティ要件への適合を証明するドキュメントの作成や、認定試験の受検などが必要です。
開発コスト以外に、この認定を取得・維持するコストがかかる旨は事前に考慮しておく必要があります。

これを回避するには「認定済みのプラットフォーム事業者」が提供するサービスを利用する方法もあります。
このようなプラットフォーム事業者はデジタル庁から認定を受けており、プラットフォーム事業者が提供しているAPIやSDKを利用することで、自社で認定を取得することなく、マイナンバーカード対応を実装することもできます。

なお、MyJCBアプリでは、PoC段階では独自実装を行い、フロントエンド・バックエンド双方の技術的な理解を深めた上で動作の検証をスピーディーに行いました。
その後、リリースまでの流れを整理し、認定取得・維持の工数およびコストを考慮した結果、最終的には認定済みプラットフォーム事業者のサービスを利用してリリースしました。

認定を取得しているプラットフォーム事業者の一部は、下記デジタル庁のページで公開されておりますのでご参考ください。

カード代替電磁的記録を利用するサービスを開始した事業者及び事例一覧|デジタル庁


おわりに

iOSアプリでマイナンバーカードを扱うにあたっての背景や実装のポイント、注意点等を解説しました。

iOSアプリでマイナンバーカードを扱うには、iOSアプリ側の実装だけでなく、 バックエンド側の復号化・検証処理の実装や、法令遵守のための認定取得など、様々な要素を考慮する必要があります。
しかしながら、これらをクリアすることで、ユーザにとって便利で安全な本人確認手段を提供できるようになります。

今後もMyJCBアプリでは、ユーザにとって便利で安全なサービスを提供できるよう努めてまいります。

JCBでは我々と一緒に働きたいという方を募集しています。
詳しい募集要項等については採用ページをご覧ください!
www.saiyo.jcb.co.jp


本文および図表中では、「™」、「®」を明記しておりません。 記載されているロゴ、製品名は各社及び商標権者の登録商標あるいは商標です。
Apple、Apple Wallet、Face ID、Touch ID、iPhoneは、米国および他の国々で登録されたApple Inc.の商標です。
iPhoneの商標は、アイホン株式会社のライセンスにもとづき使用されています。

©JCB Co., Ltd. 20︎21