こんにちは。 VRChatの沼に落ちたcordx56です。
今回は2018年のアドベントカレンダーで書いた学生証読み取りのプログラムについて、PythonではなくC++を使って少し詳しめの記事を書こうと思います。
FeliCa Library
FeliCa LibraryとはPaSoRiと呼ばれるFeliCaリーダ/ライタを用いてFeliCaにアクセスするためのWindows向けライブラリです。 無料で利用でき、導入の敷居が低いのが特徴です。
実際にFeliCa Libraryを使ったプログラムを動かしてみる
実際にFeliCa Libraryを動かすには、まずNFCポートソフトウェアをインストールする必要があります。
インストールが終わったら、FeliCa Libraryをダウンロードし、PaSoRiをパソコンに接続し、PaSoRiに学生証を置いたうえでFelicaDump.exeを実行してみましょう。 大量にデータが流れてきたと思います。 これが学生証に含まれるデータのダンプです。
学籍番号だけ確認したい場合は、cordx56/SITStudentIDLoggerCppConsoleを手元にクローンし、実行してみるのがよいでしょう。 学生証から読み取った学籍番号のみが出力されます。
FeliCaの仕様
ここから先はFeliCaの仕様を眺めつつ、実際にC++を用いて学生証にアクセスしてみます。
ポーリング
FeliCaカードの読み取りは、まず最初にポーリングと呼ばれる処理から始まります。 これはPollingコマンドにより実施され、リーダ/ライタ側から不特定多数のカードに対して呼びかけを行います。
Pollingコマンドはシステムコードをパラメータとして持ちます。 システムコードとは、リーダ/ライタがカード(システム)を特定するために用いられる2バイトのコードです。
システムはシステムコードが一致した場合のみ応答を行います。
1枚のカードで複数のシステムを持たせることも可能です。 リーダ/ライタからは各システムが1枚のカードとして認識されます。 Pollingコマンドにシステムコードをパラメータとして持たせることで、ポーリング時に目的のシステムを指定することが可能になります。
ポーリングを実施されたカードは、Pollingコマンドへの応答としてIDmおよびPMmと呼ばれるコードを返します。
IDmとは製造IDを指し、リーダ/ライタが通信相手のカードを識別するための8バイトのコードです。 カードに複数のシステムが存在する場合は、各システムにひとつずつIDmが割り当てられています。
PMmとは製造パラメータを指し、リーダ/ライタが通信相手のカードの種別および性能を識別するためのパラメータで、8バイトのコードです。
(FeliCa技術方式の各種コードについて Version 1.5.1 5ページ参照) (FeliCa技術方式の各種コードについて Version 1.5.1 8ページ参照) (FeliCa技術方式の各種コードについて Version 1.5.1 9ページ参照) (FeliCaカード ユーザーズマニュアル 抜粋版 Version 2.1 30ページ参照) (FeliCaカード ユーザーズマニュアル 抜粋版 Version 2.1 58ページ参照)
では実際にポーリングを行ってみましょう。
芝浦工業大学の学生証で学籍番号が格納されているシステムのシステムコードは0x8277です。 このシステムに対し、ポーリングを行うC++コードは次のようになります。
pasori* p;
felica* f;
p = pasori_open(NULL);
if (!p) {
std::cerr << "PaSoRi open failed!" << std::endl;
exit(1);
}
pasori_init(p);
f = felica_polling(p, 0x8277, 0, 0);
まずPaSoRiをオープンして、その結果をpasori型のポインタp
に格納しています。
PaSoRiの初期化が終わったら、ポーリングを行い、ポーリングの結果をfelica型のポインタf
に格納しています。
f
のポインタが指す先は構造体です。構造体の定義はfelicalib.hを参照してください。
構造体の定義を眺めると、IDmなどが格納されているのがわかると思います。
認証なしでの読み出し
ポーリングが終わったら、次にブロックデータを読み出しましょう。
FeliCaのファイルシステムはシステム、エリア、サービス、ブロックデータから構成されています。
サービスはブロックデータに対するアクセス方式やアクセス権限を定義しています。 サービスはアクセス権限を確認するための鍵を保持しており、認証が必要なサービスに対してはこの鍵を利用してカードとリーダ/ライタ間の認証を行います。
エリアは、ブロックデータを階層的に管理するためのものです。
Read Without Encryptionコマンドは名前の通り、認証不要のサービスからブロックデータを読み出します。 このコマンドはパラメータとしてIDm、サービス数、サービスコード、サービスコードリスト、ブロック数、ブロックリストなどを取ります。 IDmは先ほどのポーリングで取得したものを使います。
(FeliCaカード ユーザーズマニュアル 抜粋版 Version 2.1 25ページ参照) (FeliCaカード ユーザーズマニュアル 抜粋版 Version 2.1 90ページ参照)
では実際に認証なしでの読み出しを行ってみましょう。
芝浦工業大学の学生証で学籍番号が格納されているサービスのサービスコードは0x010bです。 このサービスからブロックデータを読み出してみましょう。
先ほどのポーリングを行うC++コードの続きに次のように記述します。
uint8 data[17];
data[16] = 0;
if (felica_read_without_encryption02(f, 0x010b, 0, 0, data) == 0) {
std::string datastring = (char*)data;
std::string student_id = datastring.substr(3, 7);
std::cout << student_id << std::endl;
}
felica_read_without_encryption02
関数でデータを読み込み、data
配列に値を格納しているのがわかると思います。
felica_read_without_encryption02
関数の返り値は実行結果になっています。
これが0の場合、正常に読み出しが行われたことになります。
felica_read_without_encryption02
に渡している引数を詳しく見ていきましょう。
まず一つ目の引数f
ですが、これはポーリングで取得したFeliCaカードの情報を渡しています。
次に二つ目の引数0x010b
ですが、これはサービスコードです。
次の三つ目の引数0
はモードで、使用しません(0にしてください)。
次の四つ目の引数0
はブロック番号で、読み出すブロックの番号を指定します。
これを1
にすると、学生証の有効期限が読み出せます(2021040120230331
といった形式で、開始年月日と終了年月日が格納されています)。
これで学生証から学籍番号と有効期限が読み出せました。 お疲れさまでした。
おわりに
今回の記事ではFeliCaの仕様を簡単に説明し、FeliCaを内蔵した芝浦工業大学の学生証からデータを読み出すプログラムを書きました。
今回の記事で扱ったソースコードを使ったサンプルプログラムとして、cordx56/SITStudentIDLoggerCppConsoleを用意してあります。 必要に応じて参考にしてください。
また、Pythonで学生証を読み取るライブラリであるcordx56/sit-idcardlib-pyも用意してあります。 こちらも自由にお使いください。