PaSoRiとnfcpyでNFCの中身を覗いてみる

# はじめに

- NFCやFeliCaの仕様に関しては [NFCとFeliCaについて整理する](https://qiita.com/h_tyokinuhata/items/d75afab3e5654535a7f3) でざっくりとまとめています

# どうやってNFCカード/タグを読み込むの？

- PCに外付けのNFCリーダーを接続する or NFC対応のスマホを使用する
- NFCリーダーはSONY製のPaSoRiがデファクト感ある

# どの型番のPaSoRiを使えば良いの？

- PaSoRiの製品一覧は [PaSoRi製品比較表](https://www.sony.co.jp/Products/felica/business/products/reader/comparison.html) にまとまっているため、自分のプラットフォームで動作する機種を選択する
- `RC-S300`は`RC-S380`の後継機種
- 注意点としては`RC-S300`と`RC-S300/S`はnfcpy(後述)で認識できない

# どうやって中身(データ)を見るの？

- コードから制御したい場合は主に以下の3種類
    - PC/SC(Personal Computer/Smart Card) API
        - ICカードの情報を取得するためのAPI
        - 大抵のプラットフォームで使えるっぽい(未検証)
        - そのぶん敷居が高い印象
    - [nfcpy](https://nfcpy.readthedocs.io/en/latest/index.html)
        - Python製のライブラリ
        - デファクト感ある
        - `RC-S300`、`RC-S300/S`に対応していない
            - [Issues](https://github.com/nfcpy/nfcpy/issues/214) には上がってるが実装はされていない
        - [libusb](https://libusb.info/) に依存している
    - [node-pcsc](https://www.npmjs.com/package/nfc-pcsc)
        - Node.js製のPC/SCラッパー
- コードから制御する必要がない場合
    - [NFC Tools - Pro Edition](https://play.google.com/store/apps/details?id=com.wakdev.nfctools.pro)
        - NFCリーダーのアプリ
        - 一通りの操作が可能
        - カードエミュレーションモードも一応実装されているため、動作検証等にも使用できそう

# 本題: PaSoRiとnfcpyでNFCの中身を覗いてみる

- NFCリーダーを接続後、以下のコマンドを実行
- 認識されている場合、接続しているNFCリーダーが出力される

```bash
$ lsusb
...
Bus 001 Device 004: ID 054c:06c1 Sony Corp. RC-S380/S
...
```

- nfcpyのインストール

```bash
$ pip install nfcpy
```

- nfcpyの初期設定
    - 以下のコマンドを実行するとnfcpyを正常に動作させるために実行が必要なコマンドを教えてくれる

```bash
$ python3 -m nfc
This is the 1.0.4 version of nfcpy run in Python 3.9.7
on Linux-5.13.0-37-generic-x86_64-with-glibc2.34
I'm now searching your system for contactless devices
** found usb:054c:06c1 at usb:001:017 but access is denied
-- the device is owned by 'root' but you are 'kazukichi'
-- also members of the 'root' group would be permitted
-- you could use 'sudo' but this is not recommended
-- better assign the device to the 'plugdev' group
   sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c1\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'
   sudo udevadm control -R # then re-attach device
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
Sorry, but I couldn't find any contactless device
```

- 上記で提示されたコマンドを実行後、再度以下のコマンドを実行すると、更に実行が必要なコマンドが提示されることもあるため注意する

```bash
$ python3 -m nfc
This is the 1.0.4 version of nfcpy run in Python 3.9.7
on Linux-5.13.0-40-generic-x86_64-with-glibc2.34
I'm now searching your system for contactless devices
** found usb:054c:06c1 at usb:001:004 but it's already used
-- scan sysfs entry at '/sys/bus/usb/devices/1-4:1.0/'
-- the device is used by the 'port100' kernel driver
-- this kernel driver belongs to the linux nfc subsystem
-- you can remove it to free the device for this session
   sudo modprobe -r port100
-- and blacklist the driver to prevent loading next time
   sudo sh -c 'echo blacklist port100 >> /etc/modprobe.d/blacklist-nfc.conf'
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
Sorry, but I couldn't find any contactless device
```

- 正常に設定できたら以下のような実行結果が表示される

```bash
$ python3 -m nfc
This is the 1.0.4 version of nfcpy run in Python 3.9.7
on Linux-5.13.0-40-generic-x86_64-with-glibc2.34
I'm now searching your system for contactless devices
** found SONY RC-S380/S NFC Port-100 v1.11 at usb:001:004
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs
```

- nfcpyからNFCリーダーが認識できているかの確認

```bash
$ python
Python 3.9.7 (default, Sep 10 2021, 14:59:43) 
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import nfc
>>> clf = nfc.ContactlessFrontend()
>>> clf.open('usb:054c:06c1')
True
```

- NFCカードの検出をしてみる
    - 下記のコードを実行後、NFCリーダーにNFCカード/タグをかざしてみる
    - NFCカードの情報やメモリの内容が出力されていることが分かる

```python
import nfc

clf = nfc.ContactlessFrontend("usb")
tag = clf.connect(rdwr={'on-connect': lambda tag: False})
print(tag)
print(('  \n  '.join(tag.dump())))
```

```bash:実行結果(センシティブな情報は■で伏せています)
$ pipenv run python3 app.py 
Type3Tag 'FeliCa Standard (RC-SA00/1)' ID=■■■■■■■■■■■■■■■■ PMM=■■■■■■■■■■■■■■■■ SYS=■■■■
System ■■■■ (Common Area)  
  Area 0000--FFFE  
  System FE0F (unknown)  
  Area 0000--FFFE  
    Area 1000--7FFF  
      Area 1001--7FFF  
        Random Service 64: write with key (0x1008)  
        Random Service 128: write with key (0x2008)  
        Area 8000--DFFF  
        Area 8001--AFFF  
          Random Service 512: write with key (0x8008)  
          Random Service 576: write with key (0x9008)  
          Random Service 640: write with key (0xA008)  
          Area B001--BFFF  
          Random Service 704: write with key (0xB008)  
          Random Service 705: write with key (0xB048)
```

|項目|説明|
|:--|:--|
|`Type3Tag`|NDEFのTypeが3(≒FeliCa)であることを示している|
|`RC-SA00/1`|NFCカードのICチップの型番<br>[技術情報対象製品一覧](https://www.sony.co.jp/Products/felica/business/tech-support/list.html) から確認することが可能|
|`ID`|`IDm`のこと<br>NFCリーダー/ライターが通信相手のカードを識別するための製造ID<br>上位2byteが製造者コード、続く6byteがカード識別番号|
|`PMM`|`PMm`のこと<br>NFCリーダー/ライターが通信相手のカードの種別と性能を識別するためのパラメータ|
|`SYS`|システムコード<br>システムを識別するための値<br>事業者/使用用途ごとに割り当てられる|
|`System` / `Area`|システム > エリア > サービス > ブロック という入れ子構造になっている|
|`Random Service`|ユーザが自由にブロックを指定してアクセス可能なサービス|

- 詳しくは [技術情報 FeliCa技術方式の各種コードについて](https://www.sony.co.jp/Products/felica/business/tech-support/) を参照