Android NFCとNexusSで MifareClassic を読み書きする(中編)

こんにちは、ちきんです。
前回に続き、 MifareClassicの話です。
今回は、 NdefFormatable を使って、 MifareClassic にデータを書き込めるか調査しました。
結果は、 (おそらく)検証に用いたカードの問題で「中途半端に書込みが成功」というものでした。きっとまっさらなカードなら成功したのではないかと思います。
色々興味深いこともいくつかわかったので、以下、その状況について説明していきます。

(1) NDEFデータの作成

まず、 NdefFormatable#format(NdefMessage) を実行するために、 NdefMessage を作成する必要があります。
NdefMessage は NdefRecord を複数含むものなので、 NdefRecord を作れば良いわけです。
NdefRecord は immutable な data class で、コンストラクタは、


NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload)

という形をしています。
これだけでは、何をどうしていいかわかりずらいですが、
NFC Forumの仕様書リストの「NFC Data Exchange Format (NDEF) Technical Specification」などを
見るとだいたい理解できると思います(見るためには要メンバー登録です)。
それぞれの意味は以下のようになるようです。

  • tnf: TNF(Type Name Format)のこと。NdefRecord の 定数として定義されている値をセットする。
  • type: データのタイプを表す。TNFによってどういう値が適切かが変わる。
  • id: ID. 省略も可能。何に使われるんでしょうか・・・
  • payload: 内包する任意のデータ。

TNFやTypeをどうすると、何がどう反応するのか、というのが全然わかりませんが、形式的に正しいデータを作るだけなら簡単なようです。
ということで、今回は以下のような NdefMessage を作ることにします。


NdefRecord ndefRecord = new NdefRecord(
NdefRecord.TNF_MIME_MEDIA,  // TNF
"text/plain".getBytes(),  // TYPE
new byte[]{},   // ID: 今回は無し
"Hello, NDEF!".getBytes() // payload
);
NdefRecord[] ndefRecordList = new NdefRecord[1];
ndefRecordList[0] = ndefRecord;
NdefMessage ndefMessage = new NdefMessage(ndefRecordList);

(2) NdefFormatable#format() の実行結果

書きこむ部分のコードは以下のような感じです。簡単ですね。


NdefFormatable ndefFormatable = NdefFormatable.get(tag);
if (!ndefFormatable.isConnected()) {
ndefFormatable.connect();
}
ndefFormatable.format(ndefMessage);

それでは前回使った保育園のカードに書込みをしてみます。
再起不能になると色々困るわけですが、、


ぽちっとな


おお、、 IOException の例外が。。 getMessage() は null でした。うーむ、嫌な予感です。。

どういうデータになったか確認してみると、一応各Sectorを見ることは問題なくできました。

  • 最初のセクタであるSector1 から Sector3までが何か書き換わっていて、
  • Sector4は鍵がかかっている(既知ではない)ので、どうなっているか不明。
  • Sector5は以前のままでした。

どうも、 Sector4まで書込みに行って失敗して終了したようです。 ちなみに、Sector4の鍵は保育園のシステムが使用しているんだと思います。
今回書きこもうとした NdefMessage はそれほど大きくないはずですが、
format() という名前からして、割と広範囲に初期化書込みをするのかもしれません。

Sector1 から Sector3 で、 DataBlockが更新されているのが、 Sector1 の 2つ目、3つ目のみ。
Trailerは3つとも更新されていました。
具体的には下記のようになっていました。

-- Sector1 --
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx     // ID Blockにつき省略
0F0003E1 03E103E1 03E103E1 03E103E1     // 更新
03E103E1 03E103E1 03E103E1 03E103E1     // 更新
00000000 00007877 88C10000 00000000     // 更新
-- Sector2,3 --
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
00000000 00007F07 88400000 00000000     // 更新
----------------

とりあえず、気になるのはアクセス権を表す Trailer 部分がどうなっているかです。
色々調べたところ以下のようになっていました。太字が変更点です。

■ Sector1: AccessBytes: 0x787788c1 なので、
KeyA: 不明(KEY_DEFAULT や KEY_NFC_FORUM ではない)
KeyB: KEY_DEFAULT
Trailer Access: (C1,C2,C3)=(0,1,1)
KeyA R: Never         // KeyAは 読み込む不可
KeyA W: KeyB          // KeyAは KeyBで書込み可能
Abit R: KeyA or KeyB  // アクセス権は、KeyAorBで読込み可能
Abit W: KeyB          // アクセス権は、KeyBで書込み可能
KeyB R: Never         // KeyBは、読込み不可
KeyB W: KeyB          // KeyBは、KeyBで書込み可能
Data Access: (C1,C2,C3)=(1,0,0)
Read: KeyA or KeyB    // DataBlockは、 KeyAorKeyBで読込み可能
Write: KeyB           // DataBlockは、 KeyBで書込み可能
Type: Read/Write Block// ブロックタイプは、 Read/Writeタイプ(Valueタイプではない)
■ Sector2,3: AccessBytes: 0x7f078840 なので、
KeyA: KEY_NFC_FORUM
KeyB: KEY_DEFAULT
Trailer Access: (C1,C2,C3)=(0,1,1) // Sector1と同じ
Data Access: (C1,C2,C3)=(0,0,0)
Read: KeyA or KeyB
Write: KeyA or KeyB
Type: transport configuration??

変更点をざっくりというと、

  • KeyAが KEY_DEFAULTから変更された。Sector1は不明。Sector2,3はKEY_NFC_FORUM になっている
  • KeyAによる権限が色々剥奪されているが、KeyBでは全情報を更新可能
  • KeyBが読込み不能になっている

というところです。 Sector1 の KeyA が何になっていたのか気になりますが、とりあえず、KeyBによって全て書き戻すことが可能なことがわかりました。
KeyBの読み取りがOFFになっているのも面白いです。
ただ、この実行結果は format() の途中結果に過ぎないので、正常終了した場合はもっと違う形式になっている可能性もありますので、その点はご注意下さい。

IOException の詳細な理由はわかりませんが、多分、 Sector4が読み書きできないことが原因のような気がします。
これ以上、このカードで検証することはできなさそうなので、残念ながら今回はここで終わりにします。
最後に、KeyBを使って、元データを(trailerも含めて)書き戻してみましたが、ちゃんと戻りました(良かった・・)。

(3) さいごに

やはりちゃんとした MifareClassic カードが無いとダメっぽいので、入手したらこの続きを行いたいと思います。
思いの外長引きますが、もう少し続きます・・・


Comments are closed.