はじめに
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.今回の本題
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のパケットも読めるそうです。
そうとなれば、
-
nfcpyのサンプルにある、
tagtool.py
を実行して、 - Wiresharkで監視。
- 手元にあったSuicaをタッチ。
- もう一度、
tagtool.py
を実行して、 - Wiresharkで監視。
- MEFAREの社員証をタッチ。
- 適当にデータを加工して、比較する。
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