芝浦工業大学の学生証を読み取る

こんにちは。 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も用意してあります。 こちらも自由にお使いください。

参考文献

FeliCa Library

FeliCa 技術情報