
旭川のバスカードDoCardの残高をAndroidスマートフォンで読み取るアプリを作成した。
DoCardはFeliCaカードではあるが、SuicaやPASMOといった全国交通系ICカードとは異なる独自実装である。
そのため、一般的な交通系ICカード向け残高確認アプリでは読み取れなかった。
そこで以前(2018年〜19年くらい)、USB接続のFeliCaリーダーを使ってカード内容をダンプし、サービス構造を調べたことがあった。
first commit · mimoex/DoCard_Reader@d2f681d
今回、そのときの解析結果をもとに Android から直接残高を読めるようにした。
DoCardはFeliCaだが、交通系ICカードではない
DoCardは物理層としてはFeliCaカードであり、PollingやRead Without Encryptionといった標準的な FeliCaコマンドでアクセスできる。
しかし、内部のSystem CodeやService CodeはSuicaとは異なる。
実際のダンプを見ると、DoCardのsystemは0x8DB6であった。
一方、Suica側のダンプではsystemとして0x0003が見えており、さらに他のsystemコードも複数存在していた。
この時点で、DoCardは既存の交通系ICカード読取アプリが想定しているカードとは別物であることが分かる。
ダンプして分かったDoCardのサービス
過去に調べた際、DoCardの履歴情報は0x010Fであることがわかった。
履歴サービス 0x010F
一方、service 0x010Fにはblock 0からblock 19まで、履歴らしきデータが並んでいる。
例えば実際に自分が保有しているカードのblock 0は以下の通りである。
02110e0109271a2e2201044502000226
これをバイト毎に10進数化したところ、data[0~1]が日付、末尾が残高であることがわかった。
これは過去にSuicaの履歴ダンプコードを参考に検証していたところ、同じようなデータ構造出会ったが、
処理内容はDocardではあてはまらず、TODOになっていた。
- data[0] : 月
- data[1] : 日
- data + 4 : 日付関連
- data + 9 : 使用金額
- data[11] : 連番
- data[12] : 処理内容
- data + 14 : 残高
残高取得アプリだけを作った
AndroidではNfcFを使えばFeliCaカードに直接アクセスできる。今回のアプリは、概ね以下の流れで動く。
- NFCタグ検出
- DoCardのsystem code
0x8DB6に対してPolling - IDm を取得
- 履歴サービス
0x010FをRead Without Encryption block 0から月・日・残高を取り出す
実装の中心は次の部分である。
private val SYSTEM_CODE = byteArrayOf(0x8D.toByte(), 0xB6.toByte()) // 0x8DB6
private val SERVICE_CODE_LE = byteArrayOf(0x0F.toByte(), 0x01.toByte()) // 0x010F(LE)
そして block 0の末尾2バイトから残高を復元する。
val balance = ((block0[14].toInt() and 0xFF) shl 8) or (block0[15].toInt() and 0xFF)
これでスマートフォンをDoCardにかざすだけで、残高を表示できる。

UIデザインは見逃してください…