Android NFCとNexusSで MifareClassic を読み書きする(前編)
こんにちは、ちきんです。
前回のエントリで「MifareClassicにNdefFormatableで書きこむとどうなるのか?」という疑問がありましたので、調査を続けていきます。
今回は準備として MifareClassic カードに読み書きをしたり、データのバックアップやリストアを実装してみました。
しかし、その過程でいつものようにトラブルが発生し、 解決のためにMifareClassicの仕様を調べてみたところ、
MifareClassicは後戻りできない書込みもできてしまうことなども判明しましたので、その辺りを共有したいと思います。
何せこのカードは私物の保育園の入館証なので、再起不能にしてしまったかと色々冷や汗物語はありましたが、
無駄に長くなるので、なるべく要点を記述していきたいと思います。
(1) MifareClassicの仕様
今回参考にしたのは、
Mifare Standard 4 kByte Card IC
という資料です。 Mifare Classic と呼ぶべきか Mifare Standard と呼ぶべきかわかりませんが、とりあえず MifareClassicと呼ぶことにします。
私のカードは、 MifareClassic インスタンスの応答によると、
- getSize(): 1024 // SIZE_1K
- getBlockCount(): 64 // 全体で64ブロック
- getSectorCount(): 16 // 全体で16セクタ、つまり、1セクタ4ブロック。
ということなので、 SIZE 1K です。
この資料はタイトルからして4KBのものですが、仕様はほぼ同じなのではないかと思います。
MifareClassic の仕様を箇条書きに書いてみます。
- 1つカードには複数の「セクタ」があり、1つの「セクタ」には複数の「ブロック」がある。1ブロックは16byteである。
- セクタ単位で、アクセス権を定めることができる。
- 最初のセクタの最初のブロックは、IC manufacturer data があり書込みはできない(最初のセクタでも2番目、3番目はDataBlock)。最初の4バイトがSerialNumber(=ID)、次の1byteがcheckbyte、残りが manufacturer data である。
|00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| | | SerialNumber| | manufacturer data checkbyte
- 各セクタの最後のブロック(今回のカードなら、3,7,11,,, 番目のブロック)は、「Sector Trailer」と呼ばれる部分で、2つの鍵(KeyA,KeyB)とアクセス権情報が格納されている。
|00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| | | Key A | AccessBits| Key B
- その他のDataBlockは、「read/write ブロック」か「value ブロック」である( アクセス権情報で規定される )。read/writeブロックは、単純なRead/Writeが可能で、ValueブロックはIncrementやDecrement等ができるようだ。
※今回は、 read/write ブロックのみ関心をもって調べました。 - アクセス権情報には、「Sector Trailer へのアクセス権」、「DataBlock へのアクセス権(とDataType)」 が含まれる。
※アクセス権の読み方は、参考資料の P12, P13, P14 を読んでください。細かすぎてここには書けません。。 - KeyAで行える操作、KeyBで行える操作(read/write権)が異なる(P13,P14)。
- (仕様を見る限り、今回試した限り)KeyAは端末側は読み出すことができない(端末からはWriteOnly!)。読み込むと6byteの「0x00」 が戻ってくる(コレが罠)。
- ブロックを読み書きするには、まず、該当セクタに対して鍵Aか鍵Bで認証して、認証OKならば、 AccessBits で許可されている操作ができる。
ざっとこんなところでしょうか。
また Android側のAPIで、 MifareClassic には、 鍵がいくつかstatic finalで定義されていて、
- KEY_DEFAULT: The default factory key. (値は、 6バイト全て 0xFF )
- KEY_MIFARE_APPLICATION_DIRECTORY: The well-known key for tags formatted according to the MIFARE Application Directory (MAD) specification.
(値は, 0xA0A1A2A3A4A5) - KEY_NFC_FORUM: The well-known key for tags formatted according to the NDEF on Mifare Classic specification.
(値は、0xD3F7D3F7D3F7)
とあります。
(2) MifareClassicで読み書きするには
「読み書きできるカード」があることが前提になりますが、例えば私のカードは、
- Trailerへの読み書き権限は(C1,C2,C3)=(0,0,1)で、 「KeyAを使えば、KeyA以外全て読み取り可能 & 全て上書き可能」という状態(transport configuration という言葉をよく見かけます)。
- DataBlockへの読み書き権限は(C1,C2,C3)=(0,0,0)で、 「KeyAでもKeyBでも、全て読み書き可能」という状態。
- 1つのセクタを除いて、DataBlockは全て「0xFF」だった。そのセクタは、鍵が異なるようで読み書き不能だった。
でした。
ポイントとなる手順は、例えば以下のようになります。
MifareClassic mc = MifareClassic.get(tag); // tag は android.nfc.Tag if (!mc.isConnected()) { mc.connect(); } if (mc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_DEFAULT)) { // KEY_DEFAULT を使う場合 // for read mc.readBlock(targetBlock); // for write mc.writeBlock(targetBlock, writeData); } // 以下も便利 // mc.getBlockCountInSector(sectorIndex); // セクタ内に含まれるブロック数を返す // mc.sectorToBlock(sectorIndex); // そのセクタの最初のBlockIndexを返す
注意点!
「 Sector Trailer 」ブロックには注意が必要で、ここをうっかり上書きすると、後戻りできない事態になる可能性があります。
私の場合は、とりあえず KEY_DEFAULT でアクセスできたので、
何も考えずにカードに対して「全部読みだしてファイルに書き出す」「ファイルから全部書き戻す」というプログラムを最初に作ってしまい、
KeyAの読み取りが全部「0x00」で戻ってきているのに、そのデータをKeyAに書きこんでしまいました。
つまり、KeyAを「ALL 0xFF」->「ALL 0x00」と変更してしまったわけです。
すると途端に、読み書き不能になり、とても焦りました。。
改めてKeyAを「ALL 0xFF」に書き戻すと元に戻りました。うっかり他のByte列を書き換えてなくて良かったです。。
Dumpを書き戻すのはDataBlockだけにすることにしました。
(3) おわりに
というわけで、保育園のカードは、ID情報か鍵でプロテクトしてあるブロックのみを見ているようなので、おそらく何を書いても大丈夫なきがしてきました。(さすが!いや、当然??)
次回は、 NdefFormatable を使って書きこんでみようと思います。
予想としては、KeyAやKeyBが KEY_NFC_FORUM になったり、 NdefFormatable#formatReadOnly() を使うと、アクセス権をReadOnlyにしたりするのではないかと思いますが、
やってみてからのお楽しみというところですね。