質問やフィードバックがありましたら、フォームからお願いします
本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください
前書き
iOS 11.0では、CoreNFCを通じてNFCタグの読み書きが可能ですが、ICカードの情報を読み取ることはできません。ICカードは、iOS 13以降でのみiOSデバイスによって読み取られることができます。
私は長い間NFCに興味を持っており、Suica(日本の交通ICカード)の情報を自分で読み取り、モバイルフォンで直接残高を確認できるようにしたいと考えています(すでにこの機能を提供するアプリがいくつかありますが、学習目的で自分で実装したいのです)。
インターネット上にSuicaに関する中国語や英語のリソースは比較的少ないため、数日間FeliCaのドキュメントを調査し、CoreNFC
を使って実装しました。
この記事では、まずNFCプロトコルについて説明し、その後、日本の交通ICカードで広く使用されているFeliCa
について解説し、最後にSwiftでの実装について説明します。
私はまだSwiftを学び始めたばかりなので、うまく書けていない部分があるかもしれません。お気軽にTwitterで指摘してください。
NFCとは?
NFC(Near Field Communication)は通信プロトコルで、RFID無線通信技術とも呼ばれています。
この規約は主に次のように定義されます:
- 通信規約:送信者と受信者の間でどのように通信するか
- データ交換:送信者と受信者の間でどのようにデータを交換するか
カードにデータを書き込むことは可能ですが(キーがあれば)、この記事ではFeliCaのデータを読み取る
方法に焦点を当てます。
NFCが解決する問題
ワイヤレス通信ではBluetoothやWi-Fiなどを使用して通信できますが、最大の問題はセキュリティ
とマッチング
です。
Bluetoothは通信する前にペアリングが必要です。マッチングには時間がかかる場合があり、この通信はより複雑です。
リーダーが20メートルからカード情報を検出し、直接支払いを要求できる場合、大きな問題が発生します。
そのため、NFCでは検出距離は通常数センチメートル以内であり、セキュリティを確保するだけでなく、ノイズ干渉を減らすことにも役立ちます。
FeliCa
FeliCaは、2001年にSony
によって開発されたICカード技術です。NFC Type-AおよびType-Bと比較して、速度が速いです。この速度が速い理由は、日本の通勤ラッシュのひどさかもしれません。
台湾のYouyouカードは、Philipsによって設計されたMifare製のICカードです。同時に、Mifareは世界中で広く使用されている非接触ICカードでもあります。
現在、日本ではFeliCaが広く使用されているようです。
FeliCa
は本当に速いです。日本の公共交通機関を利用すると、プラットフォームに入るときに足を止める必要がないことに気づくでしょう。
現在、Suica、ICOCA、はやかけんなどの交通ICカードで広く使用されており、これらのカードはすべてFeliCa
技術を使用しています。
交通機関だけでなく、交通ICカードを使ってコンビニエンスストアで支払うこともできます。
データセキュリティ
NFCでは、読み取れるデータと読み取れないデータがあり、正しいキーで暗号化を解除しないとデータの読み書きができないカードもあります。
AndroidとiOSは現在NFCカードを読み取ることができますが、残高やその他のデータを変更したい場合は、正しいキーが必要です。
FeliCaアーキテクチャ
FeliCaのアーキテクチャは、大きく分けてプライベートドメイン(プライベート)とコモンドメインに分かれます。
コモンドメインは、読み取れる情報を保存し、プライベートドメインは個人データやコントロールバランスなどの情報を保存し、操作するためには暗号化と復号が必要です。
コモンドフィールドは、いくつかの部分に分けられます:
- システム:カードの単位を示します。各カードにはシステムコード(2バイト)が付与されます。JIS 6319-4に従い、この値はFE00〜AA00の範囲になります。Suicaのようなカードは
0003
(この値は重要で、後で使用します)。 - エリア:ストレージスペースの情報、各サービスによって保存されるブロックの数などを含みます。
- サービス:外部アクセスのためにブロックにデータを保存します。各サービスにはサービスコード(2バイト)があり、エントリーレコードのサービスコードは
090f
のようになります。サービスはランダムサービス、周期サービス、パスサービスに分かれます。 - ブロック:データが保存される場所で、各ブロックは16バイトであり、必要なブロック数はサービスによって異なります。
カード内部の情報を読み取るだけの場合、サービス
とブロック
の二つの部分に直面します。
サービスタイプ
前述のように、サービスはランダム、周期、パスに分けられ、主にデータアクセスによって区別されます。
- ランダムサービス:自由に読み書きできるデータで、製造者によって決定されます。
- 周期サービス:履歴データのようなレコードを保存する場所です。
- パスサービス:残高や引き落としのようなものを管理する場所です。
コマンド
[FeliCa](http://www.proxmark.org/files/Documents/13.56 MHz - Felica/card_usersmanual_2.0.pdf)には多くの種類の指示があり、[FeliCaドキュメント](http://www.proxmark.org/files/Documents/13.56 MHz - Felica/card_usersmanual_2.0.pdf)で確認できます。FeliCaデータを読み取るには、いくつかのコマンドが必要です:
- polling
- request service
- Read without encryption
各コマンドには、リクエストに何が含まれるべきかを指定する対応するリクエストポケットがあります。この部分はCoreNFC
で対応する関数が提供されているため、心配する必要はありません。
FeliCaにおける通信プロセス
- pollingコマンドでカードをキャプチャします。
- Request Serviceコマンドを使用し、サービスコードリストを渡すと、カードはサービスコードが正しいか、読み取り可能かを確認します。サービスが存在しない場合やエラーがある場合は
0xFFFF
が返されます。 - Read without encryptionコマンドを使用し、サービスコードを渡すと、ブロック内の対応するデータを返します(最大16サービス)。
- このコマンドはstatus1とstatus2を返します。両方が0の場合、問題がないことを意味します。
- 0でない場合、エラーが存在することを意味します。エラーコードのステータスはFeliCaドキュメントで確認できます。
iOS実装
Appleの開発者向けサイトにある例を参照して、NFCタグの読み取り方法を確認できます。ここでは、FeliCaTag
の読み取り方法に焦点を当てます。
NFCを実装するには、まず物理的なiPhoneが必要です。シミュレーターではNFCを読み取ることはできません。
NFCTagReaderSession.isReadingAvailable
を使用して、デバイスでNFCデータが読み取れるかどうかを判断できます。
let year = data[4] >> 1 // get year bit
let month = (data[5] >> 5) & 0b1111 // get month bit
let date = data[5] & 0b11111 // get date bit
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.date(from: "20\(year)-\(month)-\(date)")!
デモ
ソースコードはgithubで見ることができます。
感想
情報は無事に読み取ることができましたが、一部の数値は予想と異なりました。
例えば、駅コードはインターネットで長時間検索しても見つかりませんでした。駅コードを正しい方法で解析できたかも疑問です。
履歴レコードは20ブロック取得できると言われていますが、これが原因で読み取りに失敗するカードもあり、安全な範囲は10ブロックです(まだテスト中です)。
内部のレコードを見ていると(私のカードは普通のチケットです)、すべての出入りが記録されるわけではないようです。カードを使って自分の出入り情報を収集したい場合、視覚化するのは簡単ではないかもしれません。
FeliCaの読み取りについては、iOSで日本の開発者が書いたライブラリTRETJapanNFCReaderがあり、非常に使いやすいです。直接使用したい場合は、これを参考にできます。SUICAの他にも、かなり多くの種類のICカードをサポートしています。運転免許証さえも読み取ることができます。
CoreNFC SDKを見たとき、FeliCa情報を読み取りたいと思いましたが、インターネット上には中国語のリソースがあまりありませんでした。
日本の実装はかなり多くありますが、サービスコードやブロックリストなどを見ることしかできず、その意味がわかりませんでした。FeliCaのドキュメントを読むのに一日かかり、自分で実装しようとしました。
また、別のタイプへのSwiftでの変換(例えば、DataをUIntに変換するなど)もかなり手間がかかりました。このコードはインターネットから得たものですが、彼が何をしているのか本当に理解できませんでしたXD、UInt(bytes: Data)
の変換方法だけは知っています。
import Foundation
extension FixedWidthInteger {
init(bytes: UInt8...) {
self.init(bytes: bytes)
}
init<T: DataProtocol>(bytes: T) {
let count = bytes.count - 1
self = bytes.enumerated().reduce(into: 0) { (result, item) in
result += Self(item.element) << (8 * (count - item.offset))
}
}
}
すべてを読み取るのは長い旅でしたね?NFCがどのように機能するのか、FeliCaとは何か、そしてCoreNFCをSwiftでどのように使用するのかを理解できていることを願っています!
この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨
☕Buy me a coffee