この記事について
この記事は SUPER STUDIO Advent Calendar 2023 の20日目の記事になります。
マイナンバーを読んでみたい
会社のアドカレなのに、弊社のプロダクトと関係ないものを作って投稿するのもどうかと思うのですが、マイナンバーカードからマイナンバーを読んでみたいのです。
そう、ほら、あれだ、たとえばいつか転売対策とかでマイナンバーによるユーザー認証が必要になっちゃったりしなかったり、ECカートだってそういう需要があるかもしれないじゃないですか。なんとかしてマイナンバーカードからマイナンバーを読んでみたいのです。
…だって、男女のマイナンバーを足して素数になるカップルをマッチングするマッチングアプリを作ってみたいと思ってしまったからです。
マイナンバーカードの仕様について
マイナンバーカードのようなICチップを積んでいるICカードは、演算機能を持ったコンピューターのようなもので、単純な読み取りでもきちんとしたプロトコルに則って通信をする必要があります。
そのような仕様については@ishihattaさんの以下の記事が詳しいです。
こちらの投稿にも書いてありますが、個人情報の扱いには注意が必要ですし、パスワードロックやカード破損の恐れもありますので、くれぐれも自己責任でお願いします。
FlutterでNFC通信を行う
NFC通信はiOSではCoreNFC、AndroidではNfcAdapterと、使うライブラリやアクセスの仕方が異なるので、Flutterのパッケージ「nfc_manager」を使います。
そしてSetupに記載の通り、Androidはマニフェストファイル、iOSはInfo.plistなどに必要な設定をしましょう。
…と、ここで今回のどハマリポイント。
iOSの設定が少しややこしかったです。
Capabilitiesの設定
FlutterプロジェクトのiOSフォルダのRunner.xcodeproj
をXcodeで開き、プロジェクトからTARGETSのSigning & Capabilitiesで、Near Field Communication Tag Reading
を追加
Entitlementsの設定
Runner.entitlementsファイルを開き、com.apple.developer.nfc.readersession.formats
にTAG
を追加
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
info.plistの設定
info.plistを開き、
-
NFCReaderUsageDescription
に特権の説明 -
com.apple.developer.nfc.readersession.iso7816.select-identifiers
に4つのID(マイナンバーカードの指定) -
com.apple.developer.nfc.readersession.felica.systemcodes
にIDをひとつ追加
<key>NFCReaderUsageDescription</key>
<string>Read My Number Card.</string>
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
<string>12fc</string>
</array>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>D392F000260100000001</string>
<string>D3921000310001010401</string>
<string>D3921000310001010402</string>
<string>D3921000310001010408</string>
</array>
マイナンバーカードはNFC TypeBなのでFelicaは関係ないと思ったのですが、Felicaも指定しないとなぜかカード読み込みのシステムダイアログが表示されませんでした。
Android/iOS共通のコード
Android/iOS共通のコードとしては、NfcManager.instance.isAvailable()
でNFCが利用可能か調べて、NfcManager.instance.startSession()
でカードの検出を行います。
// NFC通信を行う
bool isAvailable = await NfcManager.instance.isAvailable();
if (!isAvailable) {
_setMessage('NFCが使えません');
return;
}
_setMessage('カードをタッチしてください');
try {
await NfcManager.instance.startSession(
alertMessage: "カードにタッチしてください",
onDiscovered: (NfcTag tag) async {
try {
if (Platform.isIOS) {
await onDiscoveredForIos(tag);
}
if (Platform.isAndroid) {
await onDiscoveredForAndroid(tag);
}
} catch (e) {
_setMessage('Error\n${e.toString()}');
NfcManager.instance.stopSession(errorMessage: 'Error ${e.toString()}');
}
NfcManager.instance.stopSession();
},
);
} catch (e) {
_setMessage('Error\n${e.toString()}');
}
Android固有のコード
マイナンバーカードはNFC TypeB形式のカードですが、ISO7816-4コマンドで通信するためNfcB
ではなくIsoDep
を使用します。
final isoDep = IsoDep.from(tag);
if (isoDep == null) {
_setMessage('未対応のカードです');
return;
}
IsoDepの場合、APDUプロトコルのトレーラー部も含めて返却されるため、受信データの末尾2バイトがトレーラー部で、末尾の1バイトで成否判定を行います。
// READ BINARY: マイナンバー読み取り
cmd = [0x00, 0xB0, 0x00, 0x00, 0x00];
res = await isoDep.transceive(data: Uint8List.fromList(cmd));
if (res.last != 0x00) return;
s = 'マイナンバー:';
s += utf8.decode(res.sublist(3, 15));
なお動作確認はシミュレータではNFCが使えないため実機が必要になります。
サンプルとしてマイナンバーと基本4情報を読み込むようにしてみました。
思いっきり個人情報なので画像にモザイクかけますが、きちんと読み込むことができました。
iOS固有のコード
iOSではISO7816そのままのIso7816
を使用します。
final iso7816 = Iso7816.from(tag);
if (iso7816 == null) {
_setMessage('未対応のカードです');
return;
}
Iso7816
では受信データはペイロード部(res.payload
)とトレーラー部(res.statusWord1
とres.statusWord2
)に分かれていますが、アルゴリズムはAndroidと変わりません。
// READ BINARY: マイナンバー読み取り
cmd = [0x00, 0xB0, 0x00, 0x00, 0x00];
res = await iso7816.sendCommandRaw(Uint8List.fromList(cmd));
if (res.statusWord2 != 0x00) return;
s = 'マイナンバー:';
s += utf8.decode(res.payload.sublist(3, 15));
iOSもシミュレータでは動作確認できないため実機で確認しましょう。
こちらもちゃんと読み込めてますね。
ソースコード全体はこちらです。
おわりに
友人と鳥メロで飲んでたわけですよ。
そこで、「天才的なアプリの企画を思いついたのよ!」とこだわり酒場のレモンサワーのジョッキを空けながら、その友人にマイナンバー素数マッチングアプリの企画を熱弁したのです。
あのザッカーバーグだってマッチングアプリとしてFacebook立ち上げたんだぞと。
友人は卓上の炙る!!〆さばを食べながら、こう言うのです。
「オレだったら得体のしれないアプリにマイナンバーみたいなセンシティブな個人情報を預けたくないし。うん、無理。絶対無理」
こうして日本のザッカーバーグは産まれることなく、不要となったソースコードをここで供養したのでした。
素数だけに「割り切れない恋が、ここにはある」ってアプリのキャッチコピーも合わせて供養しときます。