2017年12月27日

安価な NFC タグで秘密情報を安全に携行する試み

はてなブックマークに登録

はじめに

最近 NFC まわりの調査と実験を行っています。切り口が多く奥の深い技術ですが、NFC タグ製品について調べている過程で現在もっとも広く出回っている NXP Semiconductors 製の「NTAG21x」シリーズ (ISO/IEC 14443 Type A: MIFARE Ultralight に実装されている興味深いアクセス制御機構を知りました。

NFC タグの一般的な「手軽さ」とは裏腹に、扱いを誤るとタグがあっさり使用不能になるリスクと背中合わせであるためか今のところこの機構に言及した記事やアプリケーションはあまり見かけません。しかし、仕様を理解した上で適切に取り回せばこの安価なタグ製品の使途を大きく拡げることができるでしょう。

  • 媒体が小さく軽薄で嵩張らない
  • 非接触型であるため露出の必要がなく忍ばせやすい
  • 緊急時には簡単に破壊・破棄できる

手元ではこういった物理的な特長にも注目し、NTAG21x を秘密情報の格納に利用するアイディアを形にしてみました。データ圧縮と複数タグへの分割格納にも対応しています。今回はこの NTAG21x のアクセス制御機構に関する情報の整理を行い、試作した Android アプリケーションの内容を紹介します。

ちなみに手元では実験用に NTAG213 を計 110 枚、NTAG216 を計 25 枚調達しました。どちらもまず Amazon で少量を購入し様子見を経て後顧の憂いなく使い潰せるようにより安く入手できる Aliexpress で買い足しを行いました。参考に購入元と購入時の価格を掲載します。(個人的には現時点で Aliexpress を利用する際には万一の事故に備えての措置をとっています)

NTAG213(ユーザメモリ容量 144 バイト)

NTAG216(ユーザメモリ容量 888 バイト)

なお、本記事においては NTAG メモリ内容の参照に NXP 純正の次のアプリケーションを使用しています。

1. NTAG21x のアクセス制御機構とネイティブコマンド

データシート

アクセス制御機構の概要と簡単なデモ

  • NTAG21x メモリ上の任意のページ(1 ページ = 4 バイト)以降へのアクセス要求に対し当該タグをパスワードによって保護できる
  • パスワードによる保護は Write 要求のみならず Read 要求に対しても設定可能
  • パスワード認証連続試行回数の上限値を設定することが可能であり、この回数を超過すると当該タグの認証機能は永久に封鎖されパスワード正否にかかわらず認証通過不能となる

    デモ 1: 試作アプリから NTAG213 にデータの書き込みを行い全ページの読み書きをパスワードで保護する (動画 1分39秒)

    デモ 2: 上記デモ 1 でのパスワード設定時に併せて設定した認証試行上限回数を超過する操作を行った結果タグが自爆する様子 (動画 1分2秒)

アクセス制御機構の詳細

  • データシート中の記事
    • "1. General description"
    • "8.5 Memory organization"
    • "8.6 NFC counter function"
    • "8.8 Password verification protection"
  • 要点のまとめ
    • NTAG213 の EEPROM メモリの総ページ数は 45(0h〜2Ch)。NTAG215 は 135 ページ, NTAG216 は 231 ページであり、いずれも末尾の 4 ページが Configuration Pages である。下図の NTAG213 のメモリレイアウトを参照のこと。なお 1 ページ = 4 バイト
    • Configuration 第 1 ページの第 4 バイト「AUTH0」にはパスワード保護対象とする開始ページのアドレスを指定する。このフィールドに実在ページの範囲を超えるアドレスが格納された状態ではパスワード保護は無効。出荷時のデフォルトは 0xFF
    • Configuration 第 2 ページの第 1 バイト「ACCESS」の PROT ビットが 0 ならパスワード保護の対象は Write 要求のみ。同ビットが 1 なら Read, Write 要求の両方が対象となる
    • パスワード(PWD)は 4バイト固定。Configuration 第 3 ページへ格納。出荷時の PWD のデフォルトは 0xFFFFFFFF
      • PWD は可読文字列でなくても許容されるためバリエーションは 4 バイトで 256^4 であり用途によっては意図的に非 ASCII データを適用する選択もあり得るだろう
      • 「ACCESS」の AUTHLIM にはブルートフォース攻撃対策用にパスワード認証要求(PWD_AUTH:後出)試行回数上限を設定可能。認証連続失敗回数がこの値を超えると以降の PWD_AUTH はパスワードの正否にかかわらず永久に無効となる。試行可能回数の範囲で PWD_AUTH が成功すると失敗回数を保持する内部カウンタはリセットされる。AUTHLIM の初期値は当該機能の無効を示す 000b
    • Configuration 第 4 ページの先頭 16 ビットの Password Acknowledge(PACK)はパスワード認証通過時の肯定応答値。デフォルトは 0x0000
    • パスワード保護が有効なメモリ領域の読み書きはパスワード認証を通過し PACK を得た後に可能となる
    • PWD および PACK の読み出しは不可能。両フィールドへの READ 要求に対し NTAG は常に 0x00 を返す
    • NTAG21x のパスワード保護機構は不正なメモリアクセスの防止に特化したものでありデータ暗号化等はアプリケーション側の要件
    • Configuration Pages を永続的にロックする CFGLCK 機構にも要注意
  • フィールドの整理
    (※データシートから引用した図・表を説明のために再構成しています)


  • (参考)NTAG213 初期状態のメモリダンプ:ページ 29h 以降が Configuration Pages
    ※ NXP 提供の Android アプリ TagInfo での表示
     

関連する NTAG コマンド

上記アクセス制御機構を利用する上で最低限必要な NTAG21x ネイティブコマンドおよび関連情報を示す。

  • データシート中の記事
    • "9. Command overview"
  • 要点のまとめ
    • NTAG コマンドに対する最短のレスポンスは 4ビットの ACK or NACK である
      • Ah: Acknowledge (ACK)
      • 0h: NAK for invalid argument (i.e. invalid page address)
      • 1h: NAK for parity or CRC error
      • 4h: NAK for invalid authentication counter overflow
      • 5h: NAK for EEPROM write error
    • コマンドに付与する CRC 値は ISO/IEC 14443 の規約に準拠のこと (実装例

    • GET_VERSION コマンド
      NTAG 製品のバージョン,ストレージサイズ等を得る。Configuration Pages のアドレス取得に必要
      • リクエスト
        • コマンド 1 バイト(60h) + CRC 2 バイト
      • レスポンス
        • OK: データ 8 バイト+ CRC 2 バイト
        • NG: NAK 各値
      NTAG 21x GET_VERSION レスポンスデータの内容
      
      Byte no. Description       NTAG213 NTAG215 NTAG216  Interpretation
            0  fixed Header          00h     00h     00h
            1  vendor ID             04h     04h     04h  NXP Semiconductors
            2  product type          04h     04h     04h  NTAG
            3  product subtype       02h     02h     02h  50 pF
            4  major product version 01h     01h     01h  1
            5  minor product version 00h     00h     00h  V0
            6  storage size          0Fh     11h     13h  see following information
            7  protocol type         03h     03h     03h  ISO/IEC 14443-3 compliant
      

      NTAG 製品の識別方法

      storage size の上位 7ビットを符号なし整数値 n として解釈する。使用可能なユーザーメモリの合計サイズは、最下位ビットが 0 の場合には 2^n であり最下位ビットが 1 の場合には 2^n 〜 2^(n+1) である。これを識別に利用する。

      NTAG213 での GET_VERSION レスポンス実例

      HEX: 00 04 04 02 01 00 0F 03

      --> 0x0F = BIN: 00001111 --> BIN: 0000111 = 0x07 --> 2^7 = 128, 2^(7+1) = 256

      ※ 128〜256 バイトのユーザーメモリ空間を持つ製品は NTAG213(144 バイト)

      NTAG216 での GET_VERSION レスポンス実例

      HEX: 00 04 04 02 01 00 13 03

      --> 0x13 = BIN: 00010011 --> BIN: 0001001 = 0x09 --> 2^9 = 512, 2^(9+1) = 1024

      ※ 512〜1024 バイトのユーザーメモリ空間を持つ製品は NTAG216(888 バイト)

    • READ コマンド
      所定のページアドレスから始まる 4 ページ分(16 バイト)のデータを得る
      • リクエスト
        • コマンド 1 バイト(30h)+ 開始アドレス 1 バイト + CRC 2 バイト
      • レスポンス
        • OK: データ 16 バイト+ CRC 2 バイト
        • NG: NAK 各値
    • WRITE コマンド
      所定のページアドレスへ 1 ページ分(4 バイト)のデータを書き込む
      • リクエスト
        • コマンド 1バイト(A2h)+ 対象アドレス 1 バイト + データ 4 バイト + CRC 2 バイト
      • レスポンス
        • OK: ACK
        • NG: NAK 各値
    • PWD_AUTH コマンド
      パスワードで保護された領域へのアクセス要求に先行してパスワード認証を要求する。ここで肯定応答である PACK が得られれば所定のアクセスが可能となる(PACK の値はデフォルト 0x0000 。変更可)
      • リクエスト
        • コマンド 1 バイト(1Bh)+ パスワード 4 バイト + CRC 2 バイト
      • レスポンス
        • OK: PACK 2 バイト
        • NG: NAK 各値

2. Android API による NTAG21x のハンドリングについて

手順等

  • NTAG21x は ISO/IEC 14443 Type A であるため Android でアクセス制御機構を利用する際には標準の NfcA クラスを使用する。なお MIFARE Ultralight に特化した MifareUltralight クラスも存在するがここでは前掲のネイティブコマンド操作が処理の中心であるためあえて同クラスを利用する必然性はない
  • 所定のタグの近接を検知したらまず Tag クラスの getTechList メソッドにより当該タグの TagTechnology 文字列リストを取得。リストに "android.nfc.tech.NfcA" が含まれていれば NfcA クラスの get メソッドで NfcA オブジェクトを取得し transceive メソッドにより適宜 NTAG コマンドを呼び出すことで製品の識別と必要な処理を行う。ターゲットが NTAG21x と識別されない場合は後続処理をキャンセル
  • NfcA - API level 10
    public final class NfcA

    Provides access to NFC-A (ISO 14443-3A) properties and I/O operations on a Tag.

    Acquire a NfcA object using get(Tag).

    The primary NFC-A I/O operation is transceive(byte[]). Applications must implement their own protocol stack on top of transceive(byte[]).
    • get - API level 10
      NfcA get (Tag tag)

      Get an instance of NfcA for the given tag.

      Returns null if NfcA was not enumerated in getTechList(). This indicates the tag does not support NFC-A.

      Parameters
      • tag Tag: an NFC-A compatible tag
      Returns
      • NfcA NFC-A object
    • transceive - API level 10
      byte[] transceive (byte[] data)

      Get an instance of NfcA for the given tag.

      Send raw NFC-A commands to the tag and receive the response.

      Applications must not append the EoD (CRC) to the payload, it will be automatically calculated.


      Applications must only send commands that are complete bytes, for example a SENS_REQ is not possible (these are used to manage tag polling and initialization).

      Use getMaxTransceiveLength() to retrieve the maximum number of bytes that can be sent with transceive(byte[]).

      This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with IOException if close() is called from another thread.

      Parameters
      • data byte: bytes to send
      Returns
      • byte[] bytes received in response
      Throws
      • IOException if there is an I/O failure, or this operation is cance

コードを書いて試す

  1. パスワードを設定する(write 要求からの保護)
    • NTAG21x 種別を確認
    • パスワードとして "0000" を設定
    • ページ 04h 以降を保護対象とする
              :
      
          if (isNfcA) {
            int ntagMaxPage = -1, ntagConfPage0;
            NfcA nfca = NfcA.get(tag);
            try {
              nfca.connect();
            } catch (IOException e) {
              Log.d(TAG, "NfcA.connect() err: " + e.toString());
              return;
            }
            // NTAG 種別取得
            try {
              byte[] res = nfca.transceive(new byte[]{
                (byte) 0x60, // GET_VERSION
              });
              Log.d(TAG, "GET_VERSION reslen=" + res.length + " res=" + bytesToHexString(res));
              if (res.length == 8) {
                if (res[0] == 0x00 && res[1] == 0x04 &&
                    res[2] == 0x04 && res[3] == 0x02) {
                  byte ntagStorageSize = res[6];
                  if (ntagStorageSize == 0x0F) {
                    ntagMaxPage = 45; // 0x2D = NTAG213
                  } else if (ntagStorageSize == 0x11) {
                    ntagMaxPage = 135; // 0x87 = NTAG215
                  } else if (ntagStorageSize == 0x13) {
                    ntagMaxPage = 231; // 0xE7 = NTAG216
                  }
                }
              }
            } catch (IOException e) {
              Log.d(TAG, "GET_VERSION err:" + e.toString());
            }
            if (ntagMaxPage == -1) {
              Log.d(TAG, "is not NTAG21x");
              if (nfca.isConnected()) {
                try {
                  nfca.close();
                } catch (IOException e) {
                  Log.d(TAG, "NfcA.close() err: " + e.toString());
                }
              }
              return;
            }
            // Congiguration Pages 開始位置
            ntagConfPage0 = ntagMaxPage - 4;
      
            try {
              // パスワードを設定
              int page = ntagConfPage0 + 2; // config page 2
              byte[] res = nfca.transceive(
                  new byte[]{
                      (byte) 0xA2, // WRITE
                      (byte) page,
                      '0', '0', '0', '0'
                  }
              );
              Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
      
              // 保護開始ページを設定
              page = ntagConfPage0; // config page 0
              res = nfca.transceive(
                  new byte[]{
                      (byte) 0xA2, // WRITE
                      (byte) page,
                      (byte) 0x04,
                      (byte) 0x00,
                      (byte) 0x00,
                      (byte) 0x04 // 保護開始ページ
                  }
              );
              Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
            } catch (IOException e) {
              Log.d(TAG, "WRITE err:" + e.toString());
            }
      
            if (nfca.isConnected()) {
              try {
                nfca.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          }
              :
      
    • 上のコードにより NTAG213 のページ 04h 以降への書き込みにパスワード認証通過が必須となった状況
      AUTH0 に保護開始ページ指定
      PROT ビットはデフォルトの 0 のまま
         
  2. パスワードを設定する(read / write 要求からの保護)

    NTAG21x では write 要求のみならず read 要求に対してもパスワードによる保護が可能。ただしパスワードは両者共通

    • NTAG21x 種別を確認
    • パスワードとして "0000" を設定
    • ページ 04h 以降を保護対象とする
    • PROT ビットを立て read 要求からの保護も有効に
              :
      
          if (isNfcA) {
            int ntagMaxPage = -1, ntagConfPage0;
            NfcA nfca = NfcA.get(tag);
            try {
              nfca.connect();
            } catch (IOException e) {
              Log.d(TAG, "NfcA.connect() err: " + e.toString());
              return;
            }
            // NTAG 種別取得
            try {
              byte[] res = nfca.transceive(new byte[]{
                (byte) 0x60, // GET_VERSION
              });
              Log.d(TAG, "GET_VERSION reslen=" + res.length + " res=" + bytesToHexString(res));
              if (res.length == 8) {
                if (res[0] == 0x00 && res[1] == 0x04 &&
                    res[2] == 0x04 && res[3] == 0x02) {
                  byte ntagStorageSize = res[6];
                  if (ntagStorageSize == 0x0F) {
                    ntagMaxPage = 45; // 0x2D = NTAG213
                  } else if (ntagStorageSize == 0x11) {
                    ntagMaxPage = 135; // 0x87 = NTAG215
                  } else if (ntagStorageSize == 0x13) {
                    ntagMaxPage = 231; // 0xE7 = NTAG216
                  }
                }
              }
            } catch (IOException e) {
              Log.d(TAG, "GET_VERSION err:" + e.toString());
            }
            if (ntagMaxPage == -1) {
              Log.d(TAG, "is not NTAG21x");
              if (nfca.isConnected()) {
                try {
                  nfca.close();
                } catch (IOException e) {
                  Log.d(TAG, "NfcA.close() err: " + e.toString());
                }
              }
              return;
            }
            // Congiguration Pages 開始位置
            ntagConfPage0 = ntagMaxPage - 4;
      
            try {
              // パスワードを設定
              int page = ntagConfPage0 + 2; // config page 2
              byte[] res = nfca.transceive(
                  new byte[]{
                      (byte) 0xA2, // WRITE
                      (byte) page,
                      '0', '0', '0', '0'
                  }
              );
              Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
      
              // 保護開始ページを設定
              page = ntagConfPage0; // config page 0
              res = nfca.transceive(
                  new byte[]{
                      (byte) 0xA2, // WRITE
                      (byte) page,
                      (byte) 0x04,
                      (byte) 0x00,
                      (byte) 0x00,
                      (byte) 0x04 // 保護開始ページ
                  }
              );
              Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
      
              // read 要求からの保護も有効に
              page = ntagConfPage0 + 1; // config page 1
              res = nfca.transceive(
                  new byte[]{
                      (byte) 0xA2, // WRITE
                      (byte) page,
                      (byte) 0x80, // read,write protect
                      (byte) 0x05,
                      (byte) 0x00,
                      (byte) 0x00
                  }
              );
              Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
            } catch (IOException e) {
              Log.d(TAG, "WRITE err:" + e.toString());
            }
      
            if (nfca.isConnected()) {
              try {
                nfca.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          }
              :
      
    • 上のコードにより NTAG213 のページ 04h 以降の読み書きにパスワード認証通過が必須となった状況
         
  3. パスワードを解除する
    • NTAG21x 種別を確認
    • PWD_AUTH コマンドでパスワード "0000" を提示し認証要求
    • AUTH0 に有効範囲外のページ番号 FFh を書き込むことでパスワード保護を無効化する
              :
      
          if (isNfcA) {
            int ntagMaxPage = -1, ntagConfPage0;
            NfcA nfca = NfcA.get(tag);
            try {
              nfca.connect();
            } catch (IOException e) {
              Log.d(TAG, "NfcA.connect() err: " + e.toString());
              return;
            }
            // NTAG 種別取得
            try {
              byte[] res = nfca.transceive(new byte[]{
                (byte) 0x60, // GET_VERSION
              });
              Log.d(TAG, "GET_VERSION reslen=" + res.length + " res=" + bytesToHexString(res));
              if (res.length == 8) {
                if (res[0] == 0x00 && res[1] == 0x04 &&
                    res[2] == 0x04 && res[3] == 0x02) {
                  byte ntagStorageSize = res[6];
                  if (ntagStorageSize == 0x0F) {
                    ntagMaxPage = 45; // 0x2D = NTAG213
                  } else if (ntagStorageSize == 0x11) {
                    ntagMaxPage = 135; // 0x87 = NTAG215
                  } else if (ntagStorageSize == 0x13) {
                    ntagMaxPage = 231; // 0xE7 = NTAG216
                  }
                }
              }
            } catch (IOException e) {
              Log.d(TAG, "GET_VERSION err:" + e.toString());
            }
            if (ntagMaxPage == -1) {
              Log.d(TAG, "is not NTAG21x");
              if (nfca.isConnected()) {
                try {
                  nfca.close();
                } catch (IOException e) {
                  Log.d(TAG, "NfcA.close() err: " + e.toString());
                }
              }
              return;
            }
            // Congiguration Pages 開始位置
            ntagConfPage0 = ntagMaxPage - 4;
      
            // パスワード認証
            // 通過すれば close するまで権限が持続
            boolean authOk = false;
            try {
              byte[] res = nfca.transceive(
                  new byte[]{
                      (byte) 0x1b, // PWD_AUTH
                      '0', '0', '0', '0'
                  }
              );
              Log.d(TAG, "PWD_AUTH reslen=" + res.length + " res=" + bytesToHexString(res));
              if (res.length == 2) {
                authOk = true;
              }
            } catch (IOException e) {
              Log.d(TAG, "PWD_AUTH err:" + e.toString());
            }
      
            if (authOk) {
              // パスワード保護を解除
              try {
                int page = ntagConfPage0; // config page 0
                byte[] res = nfca.transceive(
                    new byte[]{
                        (byte) 0xA2, // WRITE
                        (byte) page,
                        (byte) 0x04,
                        (byte) 0x00,
                        (byte) 0x00,
                        (byte) 0xFF // 有効範囲超のページ指定で保護は無効に
                    }
                );
                Log.d(TAG, "WRITE p" + page + " reslen=" + res.length + " res=" + bytesToHexString(res));
              } catch (IOException e) {
                Log.d(TAG, "PWD WRITE err:" + e.toString());
              }
            }
      
            if (nfca.isConnected()) {
              try {
                nfca.close();
              } catch (IOException e) {
                Log.d(TAG, "NfcA.close() err: " + e.toString());
              }
            }
          }
              :
      
    • 上のコードにより前掲 2.のコードによるパスワード保護をこのコードで解除した状況
      ここでは PWD 自体には手をつけておらず PROT ビットも 1 のままだが、AUTH0 = FFh につきユーザ領域を自由に読み書き可能な状態に戻っている
         

3. 試作した Android アプリについて

NTAG で秘密情報を扱うという考え方

ここまで見てきたように NTAG21x はパスワード設定により Write 要求のみならず Read 要求を弾くことも可能です。これは興味深い機能ですが、両者のパスワードが共通であるためたとえば次のような取り回しはできません。

  • 部外者: タグデータへのアクセス不可
  • 利用者: Read パスワードによりタグデータの読み出しが可能
  • 管理者: 読み出しに加え Write パスワードでタグデータの書き換えが可能

このように複数のロールでタグを共用したい場合ではなく、所定のタグを独占的・排他的に扱いたい場合にこそこの機能は有用でしょう。その典型的な使用例として秘密情報の格納を想起しました。以下の発想によるものです。

  • デバイスのストレージも公共のネットワークも使用しないため情報漏洩のリスクが極めて小さい
  • 薄くて小さく軟弱な媒体であるためマスターデータの保存には不向きだが、逆にデータのコピーを携行する目的には好適であり、その状況下では簡単に媒体を破壊・破棄できることが利点にもなる
  • NFC の特性上表面を覆っても読み書きができるため手近なものに忍ばせることも容易
  • パスワードで対 Read 保護を設定しておけば他者の手に渡っても一般的なリーダーやアプリではデータを読み出せない。仮に NTAG パスワード認証を試みられたとしても事前に上限回数を設定しておけば試行の過程で当該タグは永久に封鎖される
  • 記録容量の少なさはデータ圧縮と複数タグへの分割格納で補うことが可能。嵩張らないため複数枚に渡っても携行性への影響は微小

なお、こういった使途においては NFC の世界で「データ交換」を行う上での便宜としての NDEF 形式を使用する必然性はありません。ユーザメモリ全体を任意の形式で効率よく取り回せばよいでしょう。

試作中の情報収集時に、秘密情報を紙媒体で管理する「パスワードノート」という製品があることを知りました。

ストレージやネットワークを使用しない点が共通しており興味を持ちました。たしかに「手書き」には他の方法では決して及ばない様々な柔軟性があります。これがヒントとなって NTAG を手帳へ貼付して使うアイディアに至りました。ふたつの文化の利点を活用できる良い組み合わせだと思います。

実装内容

試作の仕様として以下の内容を想定しました。

  1. NTAG213, NTAG216 を対象とする
  2. NDEF は使用せず最小限のデータヘッダに続けてユーザメモリ一杯までデータを格納する
  3. テキスト形式のデータであることを前提とする。容量節約のためマルチバイト文字のエンコーディングには Unicode ではなく Shift-JIS を使用する
  4. データは gzip 形式で圧縮する
  5. データが一枚のタグに収まらない場合は複数のタグへ分割格納する。ただし、データを分割して格納したタグはそれぞれ単独での読み出しも可能とする(全タグの順次読み出しをデータ復元の前提とせず、全タグが揃わなくても部分データを柔軟に取得できるようにする)
  6. Read/Write 要求に対しメモリの全ページをパスワード保護対象とする
  7. NTAG の 4 バイトのパスワード領域へ 4 バイトの ASCII キャラクタコードを格納するのではなく指定されたパスワード文字列の CRC32 値を格納する (PWD のバリエーションは 256^4 となる)
  8. 上記の措置のため他のツールではここで設定したパスワード保護を解除不能につき解除機能を独自に実装する必要がある
  9. 総当たり攻撃への対処としてパスワード認証の試行回数に上限値(AUTHLIM)を設定することにより試行回数超過の場合には NTAG の自爆機構(PWD_AUTH 要求に対する無条件拒否)を発動させる
  10. 上記にも関連し、ここではマスターデータの保存を目的とするのではなく、コピーデータの便宜的な保持を目的とすることを前提とする

NTAG ユーザメモリの使いかた

 ========= ユーザメモリ領域全体の構成 =========

 1. page 04h - 05h に固有の管理情報を格納する
 2. page 06h 以降に gzip データを格納する

 ========= 1. 管理情報について =========

 [page 04h]
    0    1    2    3
  +----+----+----+----+
  |'t' |'t' | -- | -- |
  +----+----+----+----+

  第 1, 2 バイト
    識別子 "tt"

  第 3, 4 バイト
    予備
    ※当初、データを分割格納したタグセットの識別用領域と
      することを想定したが費用対効果に乏しいと判断し中止

 [page 05h]
    0    1    2    3
  +----+----+----+----+
  | NN | -- | NN | NN |
  +----+----+----+----+
  
  第 1 バイト
   最上位ビット:後続タグの有無  0=後続なし 1=後続あり
   下位 4 ビット:タグ連番(0h - Fh)

  第 2 バイト
   予備

  第 3, 4 バイト
   ページ 06h 以降に格納ずみの gzip データサイズ
   short ビッグエンディアン

 ========= 2. gzip データについて =========

  - テキストデータの gzip 圧縮はオンメモリで行う

  - タグへ書き込む際には gzip データの半固定ヘッダ
    10バイト(*)を除去し、読み出しの際には当該ヘッダを
    補填した上で unpack する
    (*) http://www.onicos.com/staff/iz/formats/gzip.html

  - データを複数のタグへ分割格納する場合は上記の管理情報で
    連番管理を行うが、事後に各タグから単独で部分データを
    読み出すことも可能とするために、タグへ書き込むのは
    「元データ全体を圧縮した gzip データの一部分」ではなく
    「元データを適切な位置で分割して圧縮した gzip データ」とする

ソースコード

※本記事の冒頭でも触れたように NTAG21x のアクセス制御機構の扱いには十分な注意が必要です。 試作のソースコード公開はあくまでも技術情報の紹介を目的とするものでありプログラム実行時の動作は保証しません。これを使用して何らかの損害が発生したとしても当方は一切の責任を負いません。

動作の様子

以下のデモ動画ではテストデータとして次の 4,978 バイトの英単語リストを使用。このデータを圧縮して 3 枚の NTAG216 に分割格納しています。

START, ability, abroad,
accept, access, accident,
according, account, action,
activity, actually, add,
addition, additional, address,
adult, advance, advanced,
advantage, advice, advise,
age, agency, agent,
agree, ahead, air,
airline, allow, amount,
angry, announce, announcement,
anxious, appear, appearance,
application, apply, approach,
area, arrange, arrangement,
arrival, arrive, article,
attack, attend, attention,
available, average, avoid,
aware, balance, balanced,
bar, base, basic,
bear, beat, beauty,
benefit, bill, bit,
block, blood, board,
borrow, boss, branch,
break, broad, broadcast,
brush, budget, burn,
business, busy, cab,
cable, call, cancel,
capital, care, careful,
case, cash, catalog,
catch, cause, century,
certain, certainly, chance,
change, charge, chart,
cheap, check, chemical,
choose, citizen, claim,
clear, clerk, close,
clothes, collect, collection,
comfortable, common, communication,
company, compare, complain,
complete, concern, condition,
contact, contain, content,
continue, contract, control,
convenient, conversation, copy,
corner, correct, cost,
count, couple, course,
court, cover, crash,
create, credit, cross,
crowd, crowded, customer,
daily, damage, data,
date, deal, decide,
decision, defence, degree,
delay, deliver, demand,
department, depend, deposit,
describe, design, destroy,
detail, develop, dial,
diet, difference, difficulty,
direct, direction, directly,
director, discount, discuss,
disease, disk, display,
distance, district, document,
double, doubt, downtown,
dress, drive, drop,
drug, due, earn,
earthquake, economy, edge,
education, effect, effective,
effort, electricity, employee,
empty, encourage, energy,
enjoy, enter, entrance,
equipment, event, examination,
example, excellent, except,
exciting, excuse, exercise,
exit, expect, expense,
expensive, experience, expert,
explain, express, extra,
face, fail, fair,
fall, fan, fare,
fashion, fault, favor,
favorite, feature, fee,
fence, figure, file,
fill, film, final,
fine, fire, firm,
fit, fix, flag,
flat, flood, floor,
flow, follow, following,
force, foreign, form,
former, forward, found,
foundation, free, freeze,
front, fund, furniture,
further, future, gain,
garage, gas, gather,
general, global, goal,
government, grade, graduagte,
grand, ground, guard,
guess, guide, handle,
hang, happen, hardly,
head, heart, heat,
highly, hire, hold,
host, human, hurt,
illness, image, immediately,
improve, include, income,
increase, individual, industry,
inform, information, insect,
instead, instruction, insurance,
intend, interest, international,
interview, introduce, invitation,
invite, issue, item,
job, join, judge,
knowledge, lack, land,
language, law, lawyer,
lay, lead, lend,
length, level, license,
lie, lift, likely,
limit, limitation, line,
list, load, loan,
local, location, lock,
lose, loss, luck,
lucky, luggage, mail,
main, major, majority,
manage, management, manager,
mark, market, master,
match, material, matter,
meal, means, measure,
media, medicine, mention,
message, method, mind,
minute, miss, mix,
model, modern, moment,
monthly, movemet, nation,
national, nearly, necessary,
notice, offer, office,
officer, official, operate,
operation, operator, opinion,
order, organization, own,
pack, package, pain,
part, party, pass,
passenger, patient, pay,
payment, per, performance,
performance, period, permit,
personal, pick, plain,
plan, plant, plate,
pleasure, plenty, point,
pole, policy, popular,
position, possible, post,
powerful, practice, prefer,
prepare, present, press,
prevent, price, print,
private, probably, problem,
process, produce, product,
production, professional, profit,
program, progress, project,
promise, property, protect,
protection, proud, provide,
public, publish, purpose,
puzzle, quality, quarter,
race, raise, range,
rapid, rate, reach,
ready, realize, reason,
receive, recent, recently,
recommend, record, reduce,
refer, regard, regarding,
regular, relationship, remember,
remove, rent, repair,
report, request, require,
research, reserve, responsible,
rest, result, return,
rise, room, route,
row, run, rush,
safe, safety, sail,
salary, sale, save,
schedule, seat, section,
security, sense, separate,
separately, serious, serve,
service, shake, shape,
share, ship, shock,
short, show, sign,
signal, single, situation,
skill, skin, slip,
social, solve, sort,
sound, spare, spend,
spot, spread, staff,
stage, stair, stand,
standard, state, statement,
step, stock, store,
storm, stretch, strike,
study, subject, submit,
succeed, success, suffer,
suggest, suit, supply,
support, suppose, sure,
surprise, survey, swing,
system, taste, tax,
temperature, terible, term,
tie, tight, tip,
tired, tool, total,
touch, tour, trade,
traffic, training, trip,
trouble, turn, type,
usual, valuable, value,
view, vote, want,
war, warn, way,
wear, weather, win,
wind, wire, wise,
wonder, worth, worthy,
xenophobia, yard, yarn,
yawn, yearn, yell,
yellow, yesterday, yet,
yield, yolk, young,
youth, zeal, zenith,
zigzag, zinc, zoom,
END

動画:5分1秒

※パスワード認証試行上限回数を超過した状況の動画はこちら


(tanabe)
klab_gijutsu2 at 17:00│Comments(0)NFC | Android

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント