Nfcpy on MacでFeliCaを読む（2021年2月）


久しぶりにmacにてnfcpyを利用してFeliCa(WAON)を読む機会があったのでメモ。
[以前(2016年）にも同じ趣旨の記事を書いた](https://qiita.com/zaburo/items/da0f9b1dd102565a5bb6)ことがあったのですが、2021年環境で再挑戦。

## 2016年との同じところ・違うとこ

2016年と同じところもあれば、違うところもある。基本的に簡単になってます。

* pipはbrewでは（あいかわらず）インストールできない
* nfcpyはpython3.x(3.5以上）にも対応した（2.xじゃないとダメって記事が多かったけど3.xでOKみたいです）
* インストールする周辺モジュールは減った（でも０ではない）

## やりたいこと

そもそもやりたいことは以下の通り。システムコードやサービスコードがわかっている（わからなくても調べられる）サービスであればいろいろ応用はできると思います。

* Mac + Pasori(RC-S380)を利用したい
* あるFeliCa（WAON）のメモリ構造と内容を知りたい
* WAON番号（16桁）を取り出したい
* 最終的にはラズパイで利用したのですが、ここではまずnfcpyの利用を検証

## インストール

では、環境整備から。

### pip


何はなくてもpip。Macにはpython2とpython3がインストールされていますが、python3を明示的に利用してみます。

```bash
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py
```

>そもそもget-pip.pyはpython3用のコードらしく2だとエラーになりましたね。

### Nfcpy

```bash
pip3 install nfcpy
```

### libusb

libusbが無いと実行時にエラーになるみたいなのでbrewでインストールしておきます。

```bash
brew install libusb
```

### lsusb（おまけ）

そもそもMacにPasoriが認識されているかを調べたいのでインストールしておきます。

```bash
brew install lsusb
```

```diff
lsusb

Bus 020 Device 001: ID 05ac:0267 Apple Inc. Magic Keyboard  Serial: F0T733400FKJ20TAG
Bus 020 Device 000: ID 05ac:8294 Apple Inc. Bluetooth USB Host Controller 
Bus 020 Device 019: ID 05ac:8511 Apple Inc. FaceTime HD Camera (Built-in)  Serial: CCGH17035YH0G1F0
+Bus 020 Device 005: ID 054c:06c3 Sony Corporation RC-S380/P  Serial: 0523593
Bus 000 Device 001: ID 1d6b:ISPT Linux Foundation USB 3.0 Bus 
```

>認識されていますね。

## テスト１：メモリタンプ

WAON番号を読むためのシステムコードやサービスコードの情報はネット上にありますが、他のエリアの内容も見たかったのでメモリダンプしてみます。


### 実装

[先人のノウハウ](https://tomosoft.jp/design/?p=8288)があったので参考にさせていただきました。本当にありがとうございます。
ほぼpython2を3にしているだけです。

```py:waon.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import nfc
   
def connected(tag):
  # IDm, PMM等を出力
  print(tag)
   
  if isinstance(tag, nfc.tag.tt3.Type3Tag):
    try:
      # 内容を16進数で出力する
      print(('  ' + '\n  '.join(tag.dump())))
    except Exception as e:
      print("error: %s" % e)
  else:
    print("error: tag isn't Type3Tag")
   
# タッチ時のハンドラを設定して待機する
clf = nfc.ContactlessFrontend('usb')
clf.connect(rdwr={'on-connect': connected})
```

### 実行

実行してみます。
ポイントはNDEF用のSystem 0x12FCと共通領域（0xFF00）の2つのシステムが存在しているということ。
>で、WAONの情報は0xFF00の方に書かれています。

```bash
python3 waon.py

Type3Tag 'FeliCa Standard (RC-S962)' ID=0114F100A0143000 PMM=0120220427674EFF SYS=12FC
  System 12FC (NDEF)
  Area 0000--FFFE
  System FE00 (Common Area)
  Area 0000--FFFE
    Area 3940--39FF
      Area 3941--39FF
        Random Service 229: write with key & read w/o key (0x3948 0x394B)
         0000: 70 ed 8e aa 14 10 00 70 01 00 00 00 00 00 00 00 |p......p........|
        Random Service 230: write with key & read w/o key (0x3988 0x398B)
         0000: 50 75 bc 5a 48 23 68 c5 dc b6 f5 0c c0 c2 ce 71 |Pu.ZH#h........q|
         0001: 89 ee 71 e6 12 3b be ff 96 b5 2b 69 1a 54 1e f1 |..q..;....+i.T..|
         0002: 47 76 f7 10 bb 9f 3d ec 92 86 53 f1 43 11 07 cc |Gv....=...S.C...|
         0003: fc db 41 9e cd 36 7e 33 1d 4b 69 24 c2 65 a6 36 |..A..6~3.Ki$.e.6|
         0004: d6 34 2c 24 02 0e bf c3 f7 5d 89 32 9f 2c ba 48 |.4,$.....].2.,.H|
         0005: d7 78 f1 50 56 31 d5 84 d8 22 b8 2d 93 1a e9 27 |.x.PV1...".-...'|
         0006: 31 c9 5c f2 46 c8 41 3c 58 77 87 ca 7d 05 69 53 |1.\.F.A<Xw..}.iS|
         0007: a8 3f f1 e9 f4 fb 5b 2d f4 49 a9 47 31 fd a3 fc |.?....[-.I.G1...|
         0008: ae 0e 81 cf bf 1b 37 12 a3 4d 53 f4 25 c8 95 00 |......7..MS.%...|
         0009: 09 88 52 7c 78 07 76 64 66 7c 1f 05 77 16 f9 a1 |..R|x.vdf|..w...|
         000A: fe 1f 3f 59 5b d5 7f 7e 2d 63 7e 8e 9b 99 8f db |..?Y[..~-c~.....|
         000B: 1a 15 f1 9f 5b f3 2b 6a 18 28 97 55 cd b6 ad 66 |....[.+j.(.U...f|
         000C: 84 16 0f 5e fb 4f c6 01 7a 20 35 08 75 b7 30 01 |...^.O..z 5.u.0.|
         000D: 37 56 9a 95 10 90 4f e0 36 82 df 49 80 22 6e 2e |7V....O.6..I."n.|
         000E: 49 58 ab c3 32 92 ee 5d 3b c1 33 e9 4f 59 04 79 |IX..2..];.3.OY.y|
         000F: 1b 5d 05 38 3b 43 7d 9f ff a2 d3 a2 01 11 cf 29 |.].8;C}........)|
         0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
         *     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
         0026: 09 88 52 7c 78 07 76 64 09 88 52 7c 78 07 76 64 |..R|x.vd..R|x.vd|
         0027: 09 88 52 7c 78 07 76 64 32 53 fe 51 a6 0c 7b 31 |..R|x.vd2S.Q..{1|
        Random Service 231: write w/o key (0x39C9)
         0000: 00 00 dd 00 00 2e e0 00 00 2e e0 00 00 c9 00 00 |................|
         0001: 00 1e 00 00 00 1e 00 00 e9 00 00 00 05 00 00 00 |................|
         0002: 05 00 00 e4 00 00 00 00 00 00 00 00 00 00 e8 00 |................|
         0003: 00 00 00 00 00 00 00 00 00 56 00 00 00 01 00 00 |.........V......|
         0004: 00 01 00 00 bb b6 2d 13 b3 b6 3c 55 e8 00 00 f2 |......-...<U....|
         0005: 00 00 00 32 00 00 00 32 00 00 00 00 00 00 00 00 |...2...2........|
         0006: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
         *     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
         000E: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
        Area 67C0--68FF
        Area 67C1--68FF
          Random Service 415: write with key & read w/o key (0x67C8 0x67CB)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0001: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 41 |...............A|
           0002: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0003: a9 22 ee a6 a8 e1 0a 89 80 00 00 00 00 00 00 00 |."..............|
          Cyclic Service 415: write with key & read w/o key (0x67CC 0x67CF)
           0000: 00 01 01 01 00 01 01 01 00 01 00 c0 69 00 17 21 |............i..!|
           0001: 31 95 12 43 00 00 00 00 00 00 00 00 00 00 00 00 |1..C............|
           0002: 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 |........P.......|
           0003: 0c 0f e2 92 f5 e1 89 e1 e1 40 28 03 51 19 a9 e0 |.........@(.Q...|
           0004: a4 15 b3 e0 0d a8 d6 d5 f6 fb 60 1c 2a b1 c8 45 |..........`.*..E|
           0005: 47 1c 5d e7 99 49 e0 71 1f 9b f4 6b ac f2 2e 93 |G.]..I.q...k....|
           0006: d4 56 c7 22 87 fc 2b 01 c5 12 28 db bf 49 10 e8 |.V."..+...(..I..|
          Random Service 416: write with key & read w/o key (0x6808 0x680B)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           *     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0007: 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 |....@...........|
           0008: 00 c9 1b f8 2a 58 ed e0 80 00 00 00 00 00 00 00 |....*X..........|
          Cyclic Service 416: write with key & read w/o key (0x680C 0x680F)
           0000: 39 00 e3 45 0f 46 89 2d 25 8d a5 39 13 e7 a7 35 |9..E.F.-%..9...5|
           0001: 4e 65 9f 1b f5 e1 fe 0f cc d8 a0 84 04 0f c2 99 |Ne..............|
           0002: d9 57 42 1d d0 0b 52 c8 54 90 22 d7 22 2c 21 bf |.WB...R.T.".",!.|
           0003: d0 48 92 89 78 11 08 f2 df ab f7 f3 57 81 c6 74 |.H..x.......W..t|
          Purse Service 416: direct with key & decrement with key & read w/o key (0x6810 0x6814 0x6817)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
          Random Service 417: write with key & read w/o key (0x6848 0x684B)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0002: 13 33 ab 4a b3 1b d4 a9 80 00 00 00 00 00 00 00 |.3.J............|
          Cyclic Service 417: write with key & read w/o key (0x684C 0x684F)
           0000: 69 00 17 21 31 95 12 43 01 00 00 00 00 00 00 00 |i..!1..C........|
           0001: ce 92 1a d2 38 dc c6 16 80 00 00 00 00 00 00 00 |....8...........|
          Random Service 418: write with key & read w/o key (0x6888 0x688B)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0002: 29 a1 33 1e e3 9d a2 50 80 00 00 00 00 00 00 00 |).3....P........|
          Cyclic Service 418: write with key & read w/o key (0x688C 0x688F)
           0000: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
           0001: 86 a1 a0 e8 21 ad 9d 06 80 00 00 00 00 00 00 00 |....!...........|
          Random Service 419: write with key & read w/o key (0x68C8 0x68CB)
           0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
           0002: ac ea 20 3b c1 52 16 c7 80 00 00 00 00 00 00 00 |.. ;.R..........|
          Cyclic Service 419: write with key & read w/o key (0x68CC 0x68CF)
           0000: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
           0001: 82 77 e7 99 2d 9d 7c 92 80 00 00 00 00 00 00 00 |.w..-.|.........|
```

### WAON番号を探す

WAON番号はWAONカードの裏面に刻印されている16桁の番号です。

![IMG_0255.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/6ff070a5-47e9-2d97-85db-ceb2ac3f65b8.jpeg "IMG_0255.jpg")


WAON番号の位置が知りたかったのですが、どうやら下記の2箇所にかかれているようです。
変換無し16進数表記なんですよね（苦笑）。

```
サービスコード0x67CFの
block0の12,13,14,15バイトと（0スタート）
block1の0,1,2,3バイト（0スタート）目の9バイトに書かれてるのと

サービスコード0x684Fの
block0の0,1,2,3,4,5,6,7バイト目までに16進数表記で書かれているようです。
```

>0x684Fのほうが1ブロックにかかれているから利用しやすい感じなので、そちらを利用してみます。

## テスト２：WAON番号だけ取り出す

では、上記ダンプ情報を参考に、WAON番号を取り出してみます。

### 実装

ポイントはシステムコードを指定しているところです。

```py:waonno.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import nfc

#WAONサービスコード
service_code = 0x684F

def connected(tag):

  #システムコード指定（指定しなければ最初に見つかった12FCが読まれる）
  idm, pmm = tag.polling(system_code=0xfe00)
  tag.idm, tag.pmm, tag.sys = idm, pmm, 0xfe00

  if isinstance(tag, nfc.tag.tt3.Type3Tag):
    try:
        #サービスコードの指定
        sc = nfc.tag.tt3.ServiceCode(service_code >> 6 ,service_code & 0x3f)
        #読取りブロックを指定（複数読むときは配列で指定する）
        bc = nfc.tag.tt3.BlockCode(0,service=0)
        data = tag.read_without_encryption([sc],[bc])
        #読む位置を指定：0から8byteまでを読んで16進数表記
        print(data[0:8].hex())
    except Exception as e:
      print(("error: %s" % e))
  else:
    print("error: tag isn't Type3Tag")

# タッチ時のハンドラを設定して待機する
clf = nfc.ContactlessFrontend('usb')
clf.connect(rdwr={'on-connect': connected})
```

>(servc >> 6, servc & 0x3f)は上位10bitをサービスコード、下位6bitをアトリビュート値して抽出して利用している。サービスコードを直接渡したい・・・。


### 実行

では実行。

```bash
python3 waonno.py

6900172131951243
```

> システムコードを指定せずに実行したら　error: invalid service code number or attribute　ってエラーがでます（それは12FCにはそんなサービスコードは無いから）。

## メモ

### python2からpython3へのコード変換

python2からpython3へのコード変換は以下のコマンドでできます。

```bash
#上書き
2to3 -w test.py
```

### Raspberry Piで利用する場合

原則、同じコード（概念）で動くが、いくつか注意が必要

* なぜかnfcpy(nfc)モジュールを読み込んでくれないのでPathを指定
* USBは具体的に指定。'usb'ではなく、'usb:054c:06c3'などとする。

あとは同じで動きました。

```bash
~
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages')
import nfc

#HID Keyboard
from Py_Keyboard.HID import Keyboard
kbd = Keyboard()

#WAONサービスコード
service_code = 0x684F

def connected(tag):

  #システムコード指定（指定しなければ最初に見つかった12FCが読まれる）
  idm, pmm = tag.polling(system_code=0xfe00)
  tag.idm, tag.pmm, tag.sys = idm, pmm, 0xfe00

  if isinstance(tag, nfc.tag.tt3.Type3Tag):
    try:
        #サービスコードの指定
        sc = nfc.tag.tt3.ServiceCode(service_code >> 6 ,service_code & 0x3f)
        #読取りブロックを指定（複数読むときは配列で指定する）
        bc = nfc.tag.tt3.BlockCode(0,service=0)
        data = tag.read_without_encryption([sc],[bc])
        #読む位置を指定：0から8byteまでを読んで16進数表記
        no = data[0:8].hex()
        print(no)
        #kbd.write(no+"\n")
    except Exception as e:
      print(("error: %s" % e))
  else:
    print("error: tag isn't Type3Tag")

# タッチ時のハンドラを設定して待機する
clf = nfc.ContactlessFrontend('usb:054c:06c3')
clf.connect(rdwr={'on-connect': connected})
```

実行時はsudoで。

```bash
sudo python3 waonno.py
```