Amazonで売られているNFCタグを買って読み込んでみたら、何やら色々なデータが書き込まれていました。UIDと呼ばれるカードごとに一意な情報はわかりますが、そのほかのデータは一体何なのか気になったので調べてみることにしました。

これは仕様を解説する記事ではなく、自分の頭の整理のために書いてるものになりますので、正確な情報は仕様書を確認してみてください。

今回使用したNFCタグ

今回はAmazonで売られている普通のNFCタグを購入しました。これらはNTAG213/NTAG215という規格のもので、正直Mifare Ultralight Cの違いがよくわかってません。おそらくMifare Ultralight Cの下位に属するものではないかと思ってます。
このほかにもMifare Classic 1Kのような商品も売られていますが、こちらはデータ構造が違うので今回は割愛します。

データを読み込んでみた

M5StackのRFID 2ユニットを使って、カードのデータをDUMPするプログラムを作って確認してみました。NTAG213/NTAG215のデータシートはこちらのURLからダウンロードできます。

コイン型のデータ(NTAG215 504バイト)

Card UID: 04 47 72 9E BB 2A 81 
Card Type: MIFARE Ultralight or Ultralight C
Page: 0  1  2  3  : Text
  0 : 04 47 72 B9 :  Gr 
  1 : 9E BB 2A 81 :   * 
  2 : 8E 48 00 00 :  H  
  3 : E1 10 3E 00 :   > 
  4 : 03 00 FE 00 :     
  5 : 00 00 00 00 :     
  6 : 00 00 00 00 :     
  7 : 00 00 00 00 :     
  8 : 00 00 00 00 :     
 
128 : 00 00 00 00 :     
129 : 00 00 00 00 :     
130 : 00 00 00 BD :     
131 : 04 00 00 FF :     
132 : 00 05 00 00 :     
133 : 00 00 00 00 :     
134 : 00 00 00 00 :     
135 : 04 47 72 B9 :  Gr 

データは「ページ」というブロックに区切られて、1ページ4バイトで構成されています。このNFCタグの場合は0~135までのデータが確認できました。

これを見ると0~1ページと2ページの0バイト目(以下、位置の先頭は0から数えます)がUIDにあたる部分のようです。Mifare Ultralight CのUIDは7バイトで、2バイトはチェック用のデータです。

この色が付いた部分がUIDですね。

2ページ目の1バイト目(先頭を0と数えます)はinternalとあるので、おそらく意味のあるデータではないでしょう、2~3バイト目はLock Bytesで、読み込み専用の設定のようです。全て0なので、書き込みが可能ということですね。どのビットがどの場所に対応しているか詳しく知りたい方は仕様書を見てください。データの最後の方にもLock Bytesがあるようです。(130ページ目とか)

次に進みましょう。3ページ目は以下のようになっています。

ここには自由に使用できるユーザー領域の容量が書かれています。しかし仕様書を読んでもよくわかりませんでした。唯一分かったのは2バイト目の値によって、NDEFメッセージのサイズが決まるということです。

12hの場合、144バイト
3Ehの場合、496バイト
6Dhの場合、872バイト

このNFCタグでは3Ehだったので、NDEFメッセージのサイズは496バイトということですね。どうも2バイト目の値(3Eh)を8倍した値のようです。NDEFメッセージとは何ぞや、については後述します。

補足:こちらのブログによと、E1hはNDEFメッセージが存在することを示すマジックナンバー、10hはバージョンとのことです。

続きまして、4~129ページはユーザーが自由に書き込める領域です。ほとんどは0になっていますが、なにやら意味ありげなデータが書かれています。これについては後述するNDEFメッセージの箇所で触れます。

どんどん進みましょう。

130 : 00 00 00 BD :
130ページ(82h)の0~2バイト目は先ほども出てきた読み込み専用の設定です。3バイト目のRFUIは予約値?(現状意味はない)

131 : 04 00 00 FF :
132 : 00 05 00 00 :
設定用の領域です。順番に MIRROR, RFUI, MIRROR_PAGE, AUTH0, ACCESS, RFUI, RFUI, RFUI となっています。

まずはMIRRORの値04hから。
MIRROR : 0000 0100b
→MIRROR_CONF : 00b (no ASCII mirror)
→MIRROR_BYTE : 00b
→予約 : 0b
→STRG_MOD_EN : 1b (strong modulation mode enabled)
→予約 : 00b

そもそもミラーってなんでしょう?こちらのページを見ると、URLに値を付加できる機能があるような気がします。たしかにタグを読み読み取ったら一緒にUIDやカウンター値(タッチされた回数)が付いてくれると、Webアプリケーション側でも便利でしょうね。ただURLは改竄が可能なので、認証用とかではなく、トラッキング用といった感じでしょうか。値の違いはよくわかりません。
※UIDと書きましたが、NDEFメッセージにもIDというものがあり、どちらを指しているのかよくわかりません

MIRROR_PAGE : 0000 0000b
ミラーリングの開始ページ?どう機能するのかわかりません。
AUTH0 : 1111 1111b
パスワード認証を要求するページ。デフォルトでは255ページになっていました。なので実質パスワードは要求されないということだと思います。たとえば10にすれば、10ページ以降はパスワードが必要って感じになるんでしょうかね。それとも10ページだけなのか。たぶん前者だとは思いますが。

ACCESS : 0000 0000b
→PROT, CFGLCK, RFUI, NFC_CNT_EN, NFC_CNT_PWD_PROT, AUTHLIM : 全て0
PROTは1にすると読み込み時のパスワードが必要になるぽい。
CFGLCK1にすると設定を変更できなくなるぽい。
NFC_CNT_ENはカウンターの有効化。
NFC_CNT_PWD_PROTはカウンターのパスワード保護。
AUTHLIMはパスワードの失敗を何回まで許容するか。000bは無制限。超えたらどうなるかはわからない。

132ページの1バイト目に05hという値がありますが、ここはRFUIなので意味のない値だと思います。次行きます。

133ページ目はパスワード(PWD)です。
133 : 00 00 00 00 :
パスワード認証を行うときに使用するパスワードが32bitで設定できます。読み込んだらパスワードばれちゃうじゃん!って思われるかもしれませんが、パスワードを設定するときはたぶん、PROTを1bにして運用するのだと思います。

いよいよ最後の134ページ目
134 : 00 00 00 00 :
0~1バイト目がPACKです。パスワードの検証で使われるようですが、仕様書を見てもよくわかりません。

そして存在しないはずの135ページ目
135 : 04 47 72 B9 : Gr
たぶんデータに意味はないと思います。実はプログラム上でデータを取得するときは、1ページ単位ではなく、指定したページから16バイトが返されます。実際のプログラムでは4ページ単位で取得しているので、この135ページ目というのは余ったゴミデータだと思われます。

シール型のデータ(NTAG213 144バイト)

続いてNTAG213というNFCタグのデータです。先ほどのNTAG215との違いはサイズくらいです。

Card UID: 04 D8 93 5A 22 68 80 
Card Type: MIFARE Ultralight or Ultralight C
Page: 0  1  2  3  : Text
  0 : 04 D8 93 C7 :     
  1 : 5A 22 68 80 : Z"h 
  2 : 90 48 00 00 :  H  
  3 : E1 10 12 00 :     
  4 : 01 03 A0 0C :     
  5 : 34 03 00 FE : 4   
  6 : 00 00 00 00 :     
  7 : 00 00 00 00 :     
  8 : 00 00 00 00 :     
 
 38 : 00 00 00 00 :     
 39 : 00 00 00 00 :     
 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 D8 93 C7 :     
 46 : 5A 22 68 80 : Z"h 
 47 : 90 48 00 00 :  H  

以下はNTAG213のメモリ構成になります。ユーザー領域が小さくなったくらいで、基本的に同じですね。

3ページ目の3バイト目を見ると、ユーザー領域にあるNDEFメッセージのサイズが、先ほどとは違っていることがわかります。

12hなので144バイトを意味しています。

4~39ページ目のユーザー領域も先頭がちょっと違いました。
4 : 01 03 A0 0C :
5 : 34 03 00 FE : 4
仕様書を見るとNTAG215もNTAG213の方も、仕様書に書かれたデフォルト値のようです。この部分の値については、後述するNDEFメッセージのところで調べてみます。

とりえあずNTAGの仕様については以上です。次はデータ部分について調べていきたいと思います。ハードディスクで言えば、上記の内容が物理フォーマット、NDEFメッセージが論理フォーマット、ってイメージでしょうかね。

とまあこんな感じでいろいろ調べてみましたが、普通に使う分には特に知ることもいじる必要もないデータがほとんどだと思います。UIDはライブラリの関数使えばいいですし、あとは3ページ目の3バイト目のNDEFメッセージのサイズがわかれば、NFCタグが何バイトまで読み書きできるのか判断できるので、プログラムを作るうえでこれくらい知っていれば十分だと思います。

NDEFメッセージとは何ぞや?

次はユーザー領域に書かれたNDEFメッセージについて調べていきます。NFCタグ的にはユーザー領域なわけで、ここには自由にデータを書き込んでも壊れるわけではありません。スマホと連携したりする場合などに規格に沿った形にしておけば、うまくデータをやりとりできるようになります。

NDEFメッセージに関しては、以下のページを超参考にさせていただきました。
【NFC】NDEFについて理解する

はじめに、NTAG215のユーザー領域を見てみましょう。

先頭の03hは、NDEFであることの識別子で、03hはNDEF Message のコンテナであることを意味しているようです。
次の00hはデータのバイト数で、0バイト、つまり空ですね。
その次のFEhは、データの終わりを意味しています。
つまり空っぽだよ!ってことです。

これだと面白くないので、もう少し複雑なデータが入っていたNTAG213のデータも見てみましょう。

先頭が03hではなく01hから始まっています。01hはロック対象のビットを設定するもので、次の03hがデータのバイト数なので、A0 0C 34の3バイトがロック対象のビットのようです。って何ですか?(先ほどのページには説明が無かった)

検索してみると、私が今やろうとしていることをすでにやっている方がいるようで、結論から言うとロックはかかってないみたいです。(範囲外の場所が指定されているから)

そのあとに続く 03 00 FE はNTAG215のときと同じですね。
つまり空っぽだよ!ってことです。

どうしてNTAG213にはこんなデータが入っているのでしょうか。しかも仕様書に書いてあるくらいですから理由があるんでしょうけど、ほんとになんでなんでしょうね。

NDEFメッセージを書き込んでみよう!

空のデータを見ても面白くないので、実際に意味のあるデータを書き込んで、中身がどうなっているか確かめてみたいと思います。データの書き込みにはNFC Toolsというアプリを使いました。

URLを1つだけ書き込んだ場合

URLを1件だけ書いた時のダンプデータがこちらになります。

Page: 0  1  2  3  : Text
  4 : 03 18 D1 01 :     
  5 : 14 55 04 61 :  U a
  6 : 6B 69 62 61 : kiba
  7 : 62 61 72 61 : bara
  8 : 2E 63 6F 6D : .com
  9 : 2F 62 6C 6F : /blo
 10 : 67 2F FE 00 : g/  

先頭の03hはNDEF Messageであること、次の18hはデータの長さが24バイトであることを示しています。ページ4の2バイト目(0から数える)のD1から、ページ10の1バイト目の2Fhまでが1つのデータ(NDEF Record)です。

次はそのNDEF Recordの中身を見ていきましょう。NDEF Recordのフォーマットはこちらのページに詳しく書かれています。

先頭バイトからみて
(0) NDEF Recordの構成情報(1バイト)
(1) TYPEフィールドの長さ(1バイト)
(2) PAYLOADの長さ(1または4バイト)
(3) IDフィードの長さ(1バイト、IL=0の時は省略)
(4) TYPEフィールド(任意バイト)
(5) IDフィールド(任意バイト、IL=0の時は省略)
(6) PAYLOAD(任意バイト)
という構成になっています

(0) NDEF Recordの構成情報(1バイト)
実際の値 D1h = 1101 0001b
MB : 1b (1=NDEF Messageの始まりを意味する)
ME : 1b (1=NDEF Message の終わりを意味する)
CF : 0b (0=分割されたNDEF Messageの一部ではない)
SR : 1b (1=255バイト以下のレコードを意味するフラグ)
IL : 0b (0=IDが設定されていない)
TNF : 001b (TYPEフィールドがNFC Forum well-known-type)

これを見るとこのレコードは始まりであり終わりである(なんか哲学的だなw)つまり1つだけで続きがないことがわかります。SRが1なので「(2) PAYLOADの長さ」は1バイトになります。小さなデータはとことん小さくする工夫ですね。

(1) TYPEフィールドの長さ(1バイト)
実際の値 01h
「(6) TYPEフィールドの長さは1バイトになります。

(2) PAYLOADの長さ(SRが1なので今回は1バイト)
実際の値 14h
14hなので20バイトが実際の生のデータのサイズだということがわかります。

(3) IDフィードの長さ
(5) IDフィールド
これらはILが0なので省略されます。

(4) TYPEフィールド((1)が01hなので今回は1バイト)
実際の値 55h
TYPEフィールドがNFC Forum well-known-typeとなっているとき、55hはURIを意味します。テキストの場合は54hとなり、ASCIIコードでUとTそのまんまですね。

(6) PAYLOAD(任意バイト)
実際の値 04 61 6B 69 62 61 62 61 72 61 2E 63 6F 6D 2F 62 6C 6F 67 2F
そして最後に続くのがURLです…と思いきや、先頭バイトがプロトコルを意味しています。
01h : http://www.
02h : https://www.
03h : http://
04h : https://
05h : tel:
06h : mailto:

何が何でもデータ量を小さくしようという強い意志を感じます。今回は04hだったので https:// です。そのあとに続くのはURLエンコードされた akibabara.com/blog/ という文字列で、これをくっつけるとこのブログのURLになるというわけです。

あ、まだ終わりではありません。このNDEFメッセージの次にFEhというデータがあります。これは最初に空のNFCタグを読み込んだときにも出てきた、データの最後を示す値です。

テキストを1つ書き込んだ場合

今度はHOGEというテキストを書き込んでみました。

  4 : 03 0B D1 01 :     
  5 : 07 54 02 65 :  T e
  6 : 6E 48 4F 47 : nHOG
  7 : 45 FE 00 00 : E   

D1h 01h は同じですね。PAYLOADの長さが7バイト、TYPEフィールドが54hなのでテキストを意味しています。

PAYLOADの内容 02 65 6E 48 4F 47 45
テキストは4バイトのはずなのになぜか7バイトもありますね。今回のデータの場合、先頭から3バイトはその属性を指定するためのデータのようです。

0バイト目の実際の値 02h = 0000 0010b
→エンコード : 0b (0=UTF-8、1=UTF-16)
→予約 : 0b
→言語コードの長さ: 000010b (10b=2バイト)

1~2バイト目の実際の値 656Eh(言語コードの長さが2なので)
656Ehってなんですかねこれ?
あ、ASCIIコードにするとenですね。そのままかよw

3バイト目以降の実際の値 48 4F 47 45
ASCIIコードでHOGEです。

URLとテキストの2つを書き込んでみた

最後にURLとテキストの2つのデータを書き込んでみました。

  4 : 03 15 91 01 :     
  5 : 06 55 04 78 :  U x
  6 : 2E 63 6F 6D : .com
  7 : 51 01 07 54 : Q  T
  8 : 02 65 6E 48 :  enH
  9 : 4F 47 45 FE : OGE 

03h 15hの後の 91h が1つ目のNDEFメッセージの先頭ですが、今までD1hだったのが91hになってますね。そして2つ目のNDEFメッセージの先頭は51hです。

D1h : 1101 0001b
91h : 1001 0001b MB=1 ME=0 先頭である
51h : 0101 0001b MB=0 ME=1 最後である

MB, ME のビットで分かれているだけでした。実際にスマホで読み込ませてみると、URLの部分しか認識しなかったので、どのように振舞うかはリーダー側の実装次第なのでしょう。

最後に

NFCってどうなってるの?という興味本位から調べ始めたのですが、まさかこんなに複雑だったとは思いませんでした。たったこれだけのことなのに、こんなに複雑なことをやってるんですね。いやぁ勉強になりました!

AndroidからNFCタグににURLを書き込んで、iPhoneで開くことができました!

すでにArduinoのプログラムは完成しているので、あとはNDEFのフォーマットに従ってデータを書き込んであげれば、M5Stackから任意のデータを読み書きするアプリケーションが作れそうです。

LINEで送る
Pocket