Docard

旭川のバスカードDoCardの残高をAndroidスマートフォンで読み取るアプリを作成した。

DoCard_Reader - Github

DoCardはFeliCaカードではあるが、SuicaやPASMOといった全国交通系ICカードとは異なる独自実装である。
そのため、一般的な交通系ICカード向け残高確認アプリでは読み取れなかった。

そこで以前(2018年〜19年くらい)、USB接続のFeliCaリーダーを使ってカード内容をダンプし、サービス構造を調べたことがあった。
first commit · mimoex/DoCard_Reader@d2f681d
今回、そのときの解析結果をもとに Android から直接残高を読めるようにした。

DoCardはFeliCaだが、交通系ICカードではない

DoCardは物理層としてはFeliCaカードであり、PollingRead Without Encryptionといった標準的な FeliCaコマンドでアクセスできる。
しかし、内部のSystem CodeService CodeはSuicaとは異なる。

実際のダンプを見ると、DoCardのsystem0x8DB6であった。
一方、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カードに直接アクセスできる。今回のアプリは、概ね以下の流れで動く。

  1. NFCタグ検出
  2. DoCardのsystem code0x8DB6に対してPolling
  3. IDm を取得
  4. 履歴サービス0x010FRead Without Encryption
  5. 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デザインは見逃してください…