C#でNFC(Felica/Mifare)の読み取り – 実践編 その5 – Mifare Ultra Light(NTAG21x)の読みこみ

本文上広告1



今回やること

前回、Mifare Ultra Light(NTAG21x)の仕様をがっつり読みました。

Mifare Ultra Lightとは? 自分のわかっている範囲でまとめておくと NXP NTAG 213/215/216 ...

今回は、以下のような条件で、

  • 言語 : C#
  • ライブラリ: PCSC-Sharp
  • リーダー : PaSori
  • タグ : NTAG213

次のような項目を実践してみたいと思います。

  • シリアルナンバー部分の読み取りとカードタイプの読み取り(以下のリンクで行った内容と、全体のデータの比較)
  • 今回の到達点 PCSC-SharpとPSCS.ISO7816を使用して、以下のことができるようになる。 Felica(Suica...
  • 未初期化のデータの内容(出荷時のメモリ内容)
  • NDEFライブラリの調査
  • NDEFライブラリの内容に基づいて書き込み
  • ASCIIミラーのテスト(UIDだけでも可)
  • Androidアプリで読み取ってみてどうなるか

ひとまず、以上のようなとこまで、実際にコードを書いて、確かめてみたいと思います。(消した部分は力つきました。2019/04/14 今回はやりません。ただし、NDEFのバイト配列を出力可能になったので、気が向いたらやってみたいと思います。)

実際にやってみる

UIDの読み取り

実践編その1とほぼ同じコードです。

var contextFactory = ContextFactory.Instance;

using (var context = contextFactory.Establish(SCardScope.System))
{
    var readerNames = context.GetReaders();
    if (NoReaderFound(readerNames)){
        textBoxForShow.Text += "You need at least one reader in order to run this example. "\r\n";
        return;
    }

    var readerName = ChooseRfidReader(readerNames);
    if (readerName == null)
        return;

    // 'using' statement to make sure the reader will be disposed (disconnected) on exit
    using (var rfidReader = context.ConnectReader(readerName, SCardShareMode.Shared, SCardProtocol.Any))
    {
        var apdu = new CommandApdu(IsoCase.Case2Short, rfidReader.Protocol)
        {
            CLA = 0xFF,
            Instruction = InstructionCode.GetData,
            P1 = 0x00,
            P2 = 0x00,
            Le = 0 // We don't know the ID tag size
        };

        using (rfidReader.Transaction(SCardReaderDisposition.Leave))
        {
             textBoxForShow.Text += "Retrieving the UID .... ";
             textBoxForShow.Text += "\r\n";

            var sendPci = SCardPCI.GetPci(rfidReader.Protocol);
            var receivePci = new SCardPCI(); // IO returned protocol control information.

            var receiveBuffer = new byte[256];
            var command = apdu.ToArray();

            var bytesReceived = rfidReader.Transmit(
                sendPci, // Protocol Control Information (T0, T1 or Raw)
                command, // command APDU
                command.Length,
                receivePci, // returning Protocol Control Information
                receiveBuffer,
                receiveBuffer.Length); // data buffer

            var responseApdu =
                new ResponseApdu(receiveBuffer, bytesReceived, IsoCase.Case2Short, rfidReader.Protocol);

            textBoxForShow.Text +=    "SW1: " + responseApdu.SW1.ToString() 
                                    + ", SW2: " + responseApdu.SW2.ToString() + "\r\n"
                                    + "Uid: ";
            if(responseApdu.HasData) {
                textBoxForShow.Text +=  BitConverter.ToString(responseApdu.GetData());
            } else {
               textBoxForShow.Text += "No uid received";
            }

             textBoxForShow.Text += "\r\n";

        }
    }
}

適当にボタン等を追加して、UID読み取った結果がこちらです。読み取れています。

144バイトの全データの読み取り

List<byte> getBuffer = new List<byte>();

var contextFactory = ContextFactory.Instance;

using (var context = contextFactory.Establish(SCardScope.System))
{
    var readerNames = context.GetReaders();
    if (NoReaderFound(readerNames))
    {
        textBoxForShow.Text += "You need at least one reader in order to run this example.";
        textBoxForShow.Text += "\r\n";
        //Console.ReadKey();
        return;
    }

    var readerName = ChooseRfidReader(readerNames);
    if (readerName == null)
    {
        return;
    }

    // 'using' statement to make sure the reader will be disposed (disconnected) on exit
    using (var rfidReader = context.ConnectReader(readerName, SCardShareMode.Shared, SCardProtocol.Any))
    {
        for(int i= 0; i<12; ++i)
        {
            // Pasori使用の場合はLe=10(16Byte)の場合に限定されるので4ページずつ送ればよい
            var apdu = new CommandApdu(IsoCase.Case2Short, rfidReader.Protocol)
            {
                CLA = 0xFF,
                Instruction = InstructionCode.ReadBinary,
                P1 = 0x00,
                P2 =  (byte)(i*4),
                Le = 0x10 // We don't know the ID tag size
            };

            using (rfidReader.Transaction(SCardReaderDisposition.Leave))
            {
                textBoxForShow.Text += "Read Binary : Page"\r\n";

                var sendPci = SCardPCI.GetPci(rfidReader.Protocol);
                var receivePci = new SCardPCI(); // IO returned protocol control information.

                var receiveBuffer = new byte[256];
                var command = apdu.ToArray();

                var bytesReceived = rfidReader.Transmit(
                    sendPci, // Protocol Control Information (T0, T1 or Raw)
                    command, // command APDU
                    command.Length,
                    receivePci, // returning Protocol Control Information
                    receiveBuffer,
                    receiveBuffer.Length); // data buffer

                var responseApdu =
                    new ResponseApdu(receiveBuffer, bytesReceived, IsoCase.Case2Short, rfidReader.Protocol);

                textBoxForShow.Text += "SW1: " + responseApdu.SW1.ToString()
                                        + ", SW2: " + responseApdu.SW2.ToString()+ "\r\n"
                                        + "Data: ";

                if (responseApdu.HasData){
                    textBoxForShow.Text += BitConverter.ToString(responseApdu.GetData());
                    getBuffer.AddRange(responseApdu.GetData());
                }
                else {
                    textBoxForShow.Text += "No uid received";
                }
            }
        }
    }
}

結果が以下のようになります。

4Byte毎に並べてみると以下のようなかたちになります。

00: 04 3D 8F 3E
01: C2 EC 4C 80
02: E2 48 00 00
03: E1 10 12 00
04: 01 03 A0 0C
05: 34 03 1B D1
06: 01 17 55 00
07: 68 74 74 70
08: 73 3A 2F 2F
09: 6F 66 66 69
10: 63 65 2D 66
11: 75 6E 2E 63
12: 6F 6D FE 00
13: 00 00 00 00
~~~~~~~~~~~~~~~(中略)~~~~~~
38: 00 00 00 00
39: 00 00 00 FE
40: 00 00 00 BD
41: 04 00 00 FF
42: 00 05 00 00
43: 00 00 00 00
44: 00 00 00 00
45: 04 3D 8F 3E (本来ここは不要)
46: C2 EC 4C 80 (本来ここは不要)
47: E2 48 00 00 (本来ここは不要)

UIDを読み取ったデータと前回の仕様の読み込みで見た初めのUIDの部分が同じなので、まずは正しく読み取れているかと思います。(3byte目の3Eはチェックバイトなので、UIDには反映されていない。)

Mifare Ultra Lightとは? 自分のわかっている範囲でまとめておくと NXP NTAG 213/215/216 ...

全体を読み取るためのバイト数

上のコードで肝となるが以下の部分。16*12 = 192分 PaSoriで読み取るようにしています。ただし、これは16byteで読むということで試しています。もう少しPasoriで細かいページで読めるならい余分な部分を読まずに済むかと思います。(これは私がPaSoriが16byteで読むというのをどこかのページで読んでそれをそのまま実施しているからです。Pasoriの仕様書読めばよいのですが、ちょっとおっくうになって読んでいません。正しいところを知りたい方は読んでください。)

  1. Serial number ~ CC: 4[byte/page] * 4 = 16 byte
  2. User memory (データ領域)
    • NTAG 213 144 byte ( 4[byte/page] * 36page)
    • NTAG 215 504 byte ( 4[byte/page] *126page)
    • NTAG 216 888 byte ( 4[byte/page] * 222page)
  3. Dynami lock byte ~ Configuration pagesの終わり: 4 [byte/page] * 5 = 20 byte

というようになっていますので、全体のバイト数は以下のようになっていますので、12byte分今回の例では余分に読み取っています。販売時に144byteなどと書かれているんですが、これはユーザが使用できるデータの部分のことです。

  • NTAG 213 180 byte
  • NTAG 215 540 byte
  • NTAG 216 924 byte

NTAGの仕様書より

for(int i= 0; i<12; ++i)
{
  // Pasori使用の場合はLe=10(16Byte)の場合に限定されるので4ページずつ送ればよい
  var apdu = new CommandApdu(IsoCase.Case2Short, rfidReader.Protocol)
  {
     CLA = 0xFF,
     Instruction = InstructionCode.ReadBinary,
     P1 = 0x00,
     P2 =  (byte)(i*4),
     Le = 0x10 // We don't know the ID tag size
  };
}

アンドロイドアプリでのタグ読み取りとの比較

Tagを調達し、スマホでいろいろと書き込んだサンプルを触ろうとしていた矢先にスマホを落として、全然使えなくなりました(泣) 2019/04/01 ようやくスマホを調達し、下記編集を再開しました。

いつも使うアプリ

以下のようなアプリをダウンロードして、解析・書き込みに役立てています。特にNXP純正のアプリ NFC Tag InfoTag Writerは、今回の件では有用なので、ダウンロードして、おいて損はないと思います。

アプリ読み取り例 その1

NDEFのタグは大変参考になります。ただし、書き込んだアプリの解釈次第で、NDEFの部分が思っていた書き込み方と異なるようです。以前のスマホで、この”https://office-fun.com”を書き込んだのですが、アプリがどれだったかを記録していなかったので、思い出せず。上記のアプリで別途書き込んでみた結果をまた別途記したいと思います。何が言いたいかというと、NDEFなら本来、https://の部分が省略されているはずなのですが、下記の画像を見ると、直接書き込まれているいる様子。NDEFのそんな解釈あったのかと、勉強になりました。(->省略しないタイプのURI表現もあるようです。少し驚き。)

NDEFタブ

FULL Scanタブ 上部

FULL Scanタブ 下部

上部から、スクロールした部分

パソリの読み取りと比較

左側がアプリで読みとったデータ、右側の太字が上記コードPasoriで読みとったデータです。

同じく、データの最後の部分にて、左側がアプリで読みとったデータ、右側の太字が上記コードPasoriで読みとったデータです。青線・矢印の部分が不一致だったのは少し気になるんですが、ロックとかの関係でしょうか??今回はここは深追いせずにスルーします。

NDEFのライブラリ

あと、NDEFの読み込みライブラリを探していたのですが、あまり良いのがなく、NDEFの仕様をちゃんと読むことに。。まぁ、良い勉強ですね。(2019/03/22)

上記のLockTLVを含む形を解析・書き込みできるようなクラスを作成し、先ほど公開しました。(2019/04/09)

上記のNDEFのデータ部分を参考に、全く同じデータ配列で文字列として、読み取り可能になりました。

C#でのNDEF用クラスライブラリ Simple Ndef Parser C#のNDEF用クラスライブラリ Simple Ndef P...

えらい長いNTAG215, 216のデータ

NDEFライブラリ作成しましたが、255までしか対応してないんですが、215,216はどう処理しているんだろうか??手元にないので調べようがありませんが。。

  • NTAG 213 180 byte
  • NTAG 215 540 byte
  • NTAG 216 924 byte

次回やること

本来は書き込みまでやる予定でしたが、力つきました。(2019/04/15)

ここらでいったんNFC関係は休憩します。NFCがらみはしばらくやりませんが、次回やるとしたら、

  • Pasori/アプリで最後のデータの部分の違いは何から来るのか。
  • NTAG213への書き込み
  • NTAG215,216のNDEFで255byte超えた時の挙動(他のアプリはどうしているか??)
  • 静的・動的ロックをかけた時の挙動
  • パスワード関係を調べる
  • ACRの調達
  • パスワード・AUTHLIMを設定したときの挙動
  • Excelのアドインで、2つ以上のリーダがあった時の対応
  • Excelのアドインへの落とし込みとその仕様決め
  • Excelのアドインへの落とし込み-書き込み
  • Excelのアドインへの落とし込み-読み込み・解析(シートに解析内容を書き込む)
  • Androidでもっかいコーディングをしてみる。

のようなところで、やってみたいと思います。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする