0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GNU/Linux上でSony RC-S300を使ってICカードの情報を読み取る(Rust)

Last updated at Posted at 2024-12-02

Sony RC-S300をヨドバシカメラで購入したので, 家にあるICカード(交通系IC)の情報を読み取って遊んでみます。

環境

  • ArchLinux x86_64
    • kernel: 6.12.1-arch1-1
    • DE: GNOME 47.2

パッケージのインストールと有効化

sudo pacman -S pcsclite ccid opensc libusb pcsc-tools

sudo systemctl enbale --now pcscd

PC/SCとは?

PC/SCはPersonal Computer / Smart Cardの略でざっくり説明すると、Microsoft Windows上でスマートカードを扱うためのライブラリ及びAPI実装みたいです。
LinuxではPCSCLiteという形で実装されています。

スキャンしてみる

適当なコンソールで

pcsc_scan

を実行した後、スマートカードをリーダーに近づけるとカードのATRを得ることができます。またIDmも表示されます。

IDmはスマートカードに紐付けされた一意の文字列で、基本的には他のスマートカードと被らないように設定してある...らしいです。

ATRはカードリーダがスマートカードから得られた情報そのもので、どんなカードなのかを簡易的に知ることができます(例えば、Felicaに対応しているのか等)

Rustのコード上で扱う

コードのリポジトリはここ

重要な部分だけ解説していきます。

APDUコマンドを送信してIDmを取得

Application Protocol Data Unit(以下APDU)はICカードを通信するための国際規格です。策定されているAPDUコマンドを用いてカードとの通信を行い、データの読み取りや書き込みを行います。

imp.rs

    // カードからIDmを取得する
    pub fn get_idm(&mut self, card: &pcsc::Card) {
        let idm_cmd = hex!("FF CA 00 00 00"); // どういったカードなのかを知るコマンド
        let mut buf = [0; MAX_BUFFER_SIZE];
        match card.transmit(&idm_cmd, &mut buf) {
            Ok(res_apdu) => {
                let res_len = res_apdu.len();
                let result_code = &res_apdu[res_len - 2..res_len];
                if !(*result_code.get(0).unwrap() == 0x90 && *result_code.get(1).unwrap() == 0x00) {
                    self.idm = Err(pcsc::Error::InvalidAtr); // 適当にエラーを返す(無効な値)
                    println!("> IDmの読み出しに失敗");
                    return;
                } else {
                    self.idm = Ok(Vec::from(&res_apdu[0..8]))
                }
            }
            Err(err) => {
                eprintln!("APDUコマンドの送信(IDm読み取り)に失敗: {}", err);
                self.idm = Err(pcsc::Error::CommError); // 適当にエラーを返す2(通信エラー)
                return;
            }
        }
    }

FF CA 00 00 00という16進数の文字列がAPDUコマンドになります。

ICOCAの残高を取得する

 // 試しにicocaの残高を読み取ってみる
    pub fn icoca_bal(&mut self, card: &pcsc::Card) {
        // ICOCA属性情報のサービスコードは0x008B
        // http://jennychan.web.fc2.com/format/suica.html
        let mut buf = [0; MAX_BUFFER_SIZE];
        let select_file_cmd = hex!("FF A4 00 01 02 8B 00"); // サービスコードはリトルエディアンで指定
        let read_binary_cmd = hex!("FF B0 00 00 00");

        let r = match card.transmit(&select_file_cmd, &mut buf) {
            Ok(responce) => responce,
            Err(err) => {
                eprintln!("APDUコマンドの送信に失敗: {}", err);
                std::process::exit(1);
            }
        };
        println!("select file cmd: {:02X?}", r);

        let r = match card.transmit(&read_binary_cmd, &mut buf) {
            Ok(responce) => responce,
            Err(err) => {
                eprintln!("APDUコマンドの送信に失敗: {}", err);
                std::process::exit(1);
            }
        };

        println!("read binary: {:02X?}", r);

        // 残高表示
        // 10進数へ変換
        let comb = ((r[12] as u16) << 8) | (r[11] as u16);
        println!("💰残高: {} 円", comb);
    }

これもPC/SCのAPDU仕様書(PDF)を参考にコマンドを送信します。

簡単に説明するとFF A4 00 01 02 8B 00で読み取る場所を選択し、FF B0 00 00 00でバイナリデータを取得します。なお、AP選択の際に必要なサービスコードはリトルエディアンで記入します(今回だとICOCAのサービスコードは0x008Bなので8B 00FF A4...の末尾につける。)

おわりに

ICOCAやSuicaの読み取りであれば、カードの残高だけでなく利用履歴やカードの発行場所なども知ることができるので面白そうです。

参考

ICOCA等のサービスコード

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?