37
33

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 3 years have passed since last update.

PCSC-sharpライブラリでNFCタグの読み取りソフトを作ってみた

Last updated at Posted at 2019-11-04

従業員の入退室管理のため、300枚はあろうかというMifareカードのUID(カード固有の製造番号)を読み取る必要があった。
良さげなフリーソフトが見つからなかったので、自分でコード書いてみたという話。

PCSC-sharpとは

Windows7以降に標準添付されているwinscard.dll.NET から簡単に使えるようにするラッパーライブラリ。
:point_right_tone2: 【入手元】 https://github.com/danm-de/pcsc-sharp

winscard.dllはスマートカード(ISO7816規格)をWindowsから操作するPC/SCというAPIの実装である。

PCSC-sharpに頼らず自分でDLLを叩きたいんだ!という人は、
@rhene さんの記事 https://qiita.com/rhene/items/725dfe4a6b6307731cbf
がとても参考になると思う。

NFCとは

RFIDと呼ばれる無線通信方式のひとつで、下表の通り複数の規格がある。

規格 日本における用途
Type-A (Mifare) Taspoなど
Type-B マイナンバーカード、住民基本台帳カード、運転免許証など
Type-F (FeliCa) Suica、楽天Edy、nanaco、iD、WAONなどの電子マネー
ISO/IEC 15693 物流の商品タグなど

Type-AとType-Bは事実上の世界標準で、NFC Pay(クレジットカードのタッチ決済)にも使われている。
日本ではSuicaの自動改札で通信速度が必要だったという背景からFeliCaが採用され、その他電子マネーもこぞって追随したことからFeliCaが普及した。

入退室管理に使うカードではそこまでの性能は必要ないので、安価なMifareが広く使われている。

FeliCaはSONYが開発したものだが、技術力の高さゆえの過剰性能が災いしガラパゴス化した。
4K、8Kテレビは国内メーカーを助けるための開発ではあるが、同じ道を歩まないことを願う。

ソフトウェアの作成手順

開発環境

ソフトウェア

  • Visual Studio 2019(C#)
  • Windows 10 Pro 1903

ハードウェア

  • SONY ICカードリーダ/ライタ PaSoRi RC-S380(非接触型)
  • DELL ノートパソコン Latitude 7390 内蔵NFCセンサ(非接触型)
  • DELL ノートパソコン Latitude 7390 内蔵スマートカードスロット(接触型)

image.png

NuGetパッケージ

次のNuGetパッケージをプロジェクトに追加する。

画面構成

UIコンポーネントを下図のように配置した。
image.png

C# コード

冒頭にusingディレクティブを追加する。

using PCSC;
using PCSC.Iso7816;
using PCSC.Exceptions;

アプリケーション起動時、接続されているカードリーダの名前をすべてコンボボックスに追加する。

カードリーダがひとつも接続されていなければアプリケーションを終了する。
PCSC.Exceptions.NoServiceException がスローされたら、ドライバがインストールされていないので、やはり終了する。

FormMain_Load
private void FormMain_Load(object sender, EventArgs e) {
    // リーダーの機器情報を取得
    try {
        using (var context = ContextFactory.Instance.Establish(SCardScope.System)) {
            var readerNames = context.GetReaders();
            if (readerNames == null || readerNames.Length == 0) {
                MessageBox.Show("スマートカードリーダが見つかりません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                Application.Exit();  // スマートカードリーダが無ければ終了する
            }
            foreach (var readerName in readerNames) {
                comboBoxDevice.Items.Add(readerName);  // カードリーダの名前をコンボボックスに追加
            }
        }
    } catch (NoServiceException) {
        MessageBox.Show("スマートカードリソースマネージャが稼働していません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Hand);
        Application.Exit();
    }
    comboBoxDevice.SelectedIndex = 0;
    radioButtonNone.Checked = true;

    // データグリッドビューの初期設定
    dataGridView.AllowUserToResizeColumns = true;
    dataGridView.AllowUserToResizeRows = false;
    dataGridView.RowHeadersVisible = false;
    dataGridView.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
    dataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect;

    dataGridView.Columns.Add("seq", "連番");
    dataGridView.Columns.Add("datetime", "読み取り日時");
    dataGridView.Columns.Add("uid", "UID/IDm");

    dataGridView.Columns["seq"].Width = 80;
    dataGridView.Columns["datetime"].Width = 200;
    dataGridView.Columns["uid"].Width = 250;
}

ボタンがクリックされたら、選択したカードリーダからNFCタグのUIDを読み取り、データグリッドに追加する。
読み取り用のAPDUコマンドを組み立て、送信し、結果を受信するという動きになる。

pcsc-sharp/Examples/ISO7816-4/Transmit/Program.cs を参考にしている。

private void buttonRead_Click(object sender, EventArgs e) {
    var readerName = comboBoxDevice.Text;  // 選択中のカードリーダ名を得る

    using (var context = ContextFactory.Instance.Establish(SCardScope.System)) {
        try {
            using (var rfidReader = context.ConnectReader(readerName, SCardShareMode.Shared, SCardProtocol.Any)) {
                // APDUコマンドの作成
                var apdu = new CommandApdu(IsoCase.Case2Short, rfidReader.Protocol) {
                    CLA = 0xFF,
                    Instruction = InstructionCode.GetData,
                    P1 = 0x00,
                    P2 = 0x00,
                    Le = 0 // We don't know the ID tag size
                };
                // 読み取りコマンド送信
                using (rfidReader.Transaction(SCardReaderDisposition.Leave)) {
                    var sendPci = SCardPCI.GetPci(rfidReader.Protocol);
                    var receivePci = new SCardPCI(); // IO returned protocol control information.

                    var receiveBuffer = new byte[256];
                    var command = apdu.ToArray();

                    var bytesReceived = rfidReader.Transmit(
                        sendPci, // Protocol Control Information (T0, T1 or Raw)
                        command, // command APDU
                        command.Length,
                        receivePci, // returning Protocol Control Information
                        receiveBuffer,
                        receiveBuffer.Length); // data buffer

                    var responseApdu = new ResponseApdu(receiveBuffer, bytesReceived, IsoCase.Case2Short, rfidReader.Protocol);
                    if (responseApdu.HasData) {
                        // バイナリ文字列の整形
                        StringBuilder id = new StringBuilder(BitConverter.ToString(responseApdu.GetData()));
                        if (radioButtonNone.Checked) {
                            id.Replace("-", string.Empty);
                        } else if (radioButtonSpace.Checked) {
                            id.Replace("-", " ");
                        }
                        // データグリッドビューに結果を追加し、連番をカウントアップする
                        int seq = (int)numericUpDown.Value;
                        DateTime dt = DateTime.Now;
                        dataGridView.Rows.Add(seq, dt.ToString("yyyy/MM/dd HH:mm:ss"), id.ToString());
                        numericUpDown.Value = ++seq;
                    } else {
                        MessageBox.Show("このカードではIDを取得できません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    }
                }
            }
        } catch (RemovedCardException) {
            MessageBox.Show("スマートカードが取り外されたため、通信できません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Hand);
        } catch (PCSCException ex) {
            MessageBox.Show(ex.Message, "スマートカードエラー", MessageBoxButtons.OK, MessageBoxIcon.Hand);
        }
    }
}

実行結果サンプル

image.png

37
33
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
37
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?