9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WebUSBでFeliCaのIDmとMIFAREのIDも読み込む

Last updated at Posted at 2020-04-01

はじめに

WebUSBでFeliCaの一意なIDであるIDmを読む / Qiita
こちらの記事を参考に、FeliCaだけではなく、MIFAREのIDも拾いたい。
いや、むしろ、どちらも拾えるものをということで、公開していただいているgithubをフォークして、書き加えてみましたので、
経緯、やったこと、調べたことをこちらにまとめておこうと思います。

結果のソースだけあればいいという人は、完成物をDLしてご利用ください。

新バージョンを公開しました

2023/06/16にRC-S300に対応しました。以下に別途記事を掲載しています。
合わせてご確認ください。

注意

それらしく動いてますが、pythonは読んだことないわ、仕様書は難しくて頭痛くなるわで、
サンプルのパケットを解析して、コピペするという作業をしてますので、
追加した内容については、どうして動いているのかということは分かっていません。
使う際は自己責任で。もしくはコメントを頂ければ。

完成物

https://github.com/marioninc/webusb-felica
元ネタと同様で、権利フリーです。

要件

Google Chrome最新(2020/04/01現在)
PaSori(RC-S380)
MIFARE Classicカード
Windows10(MacOSや、Linuxですと、ドライバーを書き換えず動くようです) (Windowsで)WebUSBでPasoriを扱ってみる / Qiita

元ソースとの変更箇所

####1.Google Chromeのバージョンアップでの仕様変更対応

最近(2019/03/27)、自分で書いた方法に従って実験してみたのですが、Windowsで動作させる事が出来なくなっていました。
下記の様なエラーが発生して、正常に動作しません。
原因は不明なのですが、変わった事と言えば、ChromeのVersionが上がった位なので、それが何か影響しているのでしょうか…。
後は、Windows 10のUpdateが入ったこと、エディションがHomeからProに変わった事くらいですが、それは関係無いと思っています。
因みに解決策は下記の通り。
ソースコード(html)をローカルに保存して、185行目位にある、protocolCodeの代わりに、productIdを指定します。

引用元:(Windowsで)WebUSBでPasoriを扱ってみる | 補足(2019/3/27追記) / Qiita
についての修正

2.今回の本題

MIFAREのID取得部分
await send(device,[0x00,0x00,0xff,0xff,0xff,0x03,0x00,0xfd,0xd6,0x06,0x00,0x24,0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd707002200
  
  await send(device,[0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x00,0x02,0x03,0x0f,0x03,0x13,0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd701002800
  
  await send(device, [0x00, 0x00, 0xff, 0xff, 0xff, 0x28, 0x00, 0xd8, 0xd6, 0x02, 0x00, 0x18, 0x01, 0x01, 0x02, 0x01, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x08, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0e, 0x04, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x06, 0x4b, 0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd703002600

  await send(device, [0x00,0x00,0xff,0xff,0xff,0x0c,0x00,0xf4,0xd6,0x02,0x01,0x00,0x02,0x00,0x05,0x01,0x00,0x06,0x07,0x07,0x0b,0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd703002600

  await send(device, [0x00,0x00,0xff,0xff,0xff,0x05,0x00,0xfb,0xd6,0x04,0x36,0x01,0x26,0xc9,0x00]);
  await receive(device, 6);
  await receive(device, 20);
  // <<< 0000ffffff0900f7d705000000000804001800

  await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x02,0x04,0x01,0x07,0x08,0x14,0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd703002600

  await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x02,0x01,0x00,0x02,0x00,0x25,0x00]);
  await receive(device, 6);
  await receive(device, 13);
  // <<< 0000ffffff0300fdd703002600

  await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x04,0x36,0x01,0x93,0x20,0x3c,0x00]);
  await receive(device, 6);
  let idt = (await receive(device, 22)).slice(15, 19);
  // <<< 0000ffffff0c00f4d705000000000800000000490c00
  if (idt.length > 2) {
    let idtStr = '';
    for (let i = 0; i < idt.length; i++) {
      if (idt[i] < 16) {
        idtStr += '0';
      }
      idtStr += idt[i].toString(16);
    }
    idmMessage.innerText = "Card Type : MIFARE  カードのID: " + idtStr;
    idmMessage.style.display = 'block';
    waitingMessage.style.display = 'none';
    return;
  } else {
    idmMessage.style.display = 'none';
    waitingMessage.style.display = 'block';
  }

の2か所です。

なぜ改造したのか

この度、WEB上で動く勤怠管理システム上で、ICカードタッチで出退勤をしたいという話をいただきました。
Google先生に聞いてみると元ネタの記事が出てきたので、
これで簡単にできそうだぞと、とりあえずPaSoriを用意して、社員証をかざしても、あれ、IDが出ない……

このIDカードFeliCa対応のものではなく、MIFARE Classicと呼ばれる規格のもののようで、
使い捨てを前提として、IDが一意ではない(IDが被らないように管理する機関がない)カードらしいのです。
このことが原因で、IDを取得するためのリクエストコマンドも違えば、返ってくる応答も違うという具合で、FeliCa的にはIDmが取得できなかっただけでした。

さて、どうしたものかという話

そもそもデバイス関係は専門外で、JIS規格の仕様書は入手しても、不慣れなので読むのが大変。
元ネタの元ネタになっているライブラリはpythonで書かれているようで、pythonも触ったことがない。
nfcpyは、Windows で nfcpy のセットアップ / Qiitaを見ながら、
とりあえず、動くようにはなりましたが、元ネタの記事みたいに、
ライブラリを読み解いて自分のものにはできず、うまく実装までこぎつけられませんでした。

ライブラリが読めないなら、パケットを読めばいいじゃない

WEBUSBというものは、直接、パケットを叩くかなり低レイヤーな印象のものです。とどこかの記事で書かれていました。
つまり、意味はちゃんと分かっていなくても、通信さえマネできれば動くということではないか。
そう。ライブラリが読めないなら、直接パケットを読めばいいじゃない。
というわけで、パケットを盗み見るなら、おなじみWireshark
実は、標準の機能だけで、TCPの通信だけではなくUSBのパケットも読めるそうです。

そうとなれば、

  1. nfcpyのサンプルにある、tagtool.pyを実行して、
  2. Wiresharkで監視。
  3. 手元にあったSuicaをタッチ。
  4. もう一度、tagtool.pyを実行して、
  5. Wiresharkで監視。
  6. MEFAREの社員証をタッチ。
  7. 適当にデータを加工して、比較する。
読んだ結果(MIFARE)
out // FeliCaのIDmを取得するコマンド
0000ffffff0a00f6d6046e000600ffff0100b300
in // カードがない状態と同じ
0000ffffff0600fad70580000000a400

out
0000ffffff0300fdd606002400
in
0000ffffff0300fdd707002200

out
0000ffffff0600fad60002030f031300
in
0000ffffff0300fdd701002800

out
0000ffffff2800d8d6020018010102010300040005000600…
in
0000ffffff0300fdd703002600

out
0000ffffff0c00f4d602010002000501000607070b00
in
0000ffffff0300fdd703002600

out // カードを識別しているコードぽい
0000ffffff0500fbd604360126c900
in //MIFAREを置いているときの状態
0000ffffff0900f7d705000000000804001800

out
0000ffffff0600fad602040107081400
in
0000ffffff0300fdd703002600

out
0000ffffff0600fad602010002002500
in
0000ffffff0300fdd703002600

out // MIFAREのIDを取得するコード
0000ffffff0600fad604360193203c00
in // *がMIFAREのID
0000ffffff0c00f4d7050000000008**********0c00

こんな感じになっていました。
outがRC-380に送信しているパケット
inがnfcpyに返信されたパケットです。

…にっているところは、ログを書き出したときに、長すぎて省略されてしまったものです。
ソースコードに乗せてあります。

ここまでの部分では、待機状態でループ処理が走っています。

下から3番目の
0000ffffff0600fad602040107081400から3つは、Suicaや、タッチしていない状態では、一度も流れていないコマンドでした。
その上のカードを識別しているコードぽいの部分で、初めて返信パケットに変化があり、そこから分岐がされているようです。

カードを識別しているコードぽい
では、未検出時とSuicaの時は、0000ffffff0600fad70580000000a400でした。

実装

今回は、決済をしたいわけでも、なにかデータを書き込みたいわけでもないので、IDが読めれば十分でした。
ですので、せっかく分岐点を見つけたのですが、FeliCaの読み込みに失敗したら、MIFAREのIDを読み込んでみるという感じで実装しました。
FeliCaのIDmを読んだ直後にMIFAREのIDを読みに行っても、IDは返ってこなかったので、
上の結果のFeliCaのIDmを取得するコマンドのすぐあとから、コピペして、実装しました。

感想など

今回はかなり無理やりな感じがします。よくわかってないけど動いたはよろしくないですね。
実際問題、IDを見るだけならこれでもいいかもしれませんが、読み書きをする場合はちゃんと仕様書を読まないといけないなと。

pythonのライブラリを読み解こうとしたり、ICカードの仕様書を読み解こうとしたり、USBのパケットをのぞいたり……
色々なことがやって見れたので面白くはありましたが、ちゃんと作らないと規模が大きくなると使えないなあ。

解説など、いただける方がいれば、拝読させていただきます。

使用したもの

python3
nfcpy
libusb-1.0.dll
Wireshark
Zadig

参考文献

WebUSBでFeliCaの一意なIDであるIDmを読む / Qiita
(Windowsで)WebUSBでPasoriを扱ってみる / Qiita
WebUSBことはじめ / Qiita
WiresharkでUSBメモリのパケットキャプチャしてみた! / LoT ラブオーティー
Windows で nfcpy のセットアップ / Qiita

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?