学生証をdumpしてみた

まえがき

今年の四月から私の大学の学生証が新しくなりました。

コロナの影響で、学校が閉鎖される前に行ったときにカードリーダーをみるとFelicaのリーダーになっていたのでFelicaらしいです。

今更ながら、家にあったのFelicaのリーダーが学生証の中のデータを見てました。

環境

Virtual Box Ubuntu18.04

パソリ RC-S320

準備

S320はnfcpyというPythonのライブラリに対応していないので、libpafeを使用する。

  1. Githubからlibpafeをcloneする
$ git clone https://github.com/rfujita/libpafe.git 
  1. configureコマンドを実行して足りないものを入れる
$ cd libpafe
$ ./configure

私の環境では、以下のものが必要でした。

$ sudo apt install make gcc libuseb-dev pkg-config
  1. インストール
$ make
$ sudo make install
  1. 動作確認
$ sudo ./tests/pasori_test
PaSoRi (RC-S320)
 firmware version 1.40
Echo test... success
EPROM test... success
RAM test... success
CPU test... success
Polling test... success

こんな感じなのがでればOK

dump結果

$ sudo ./tests/felica_dump > dump.txt
$ cat dump.txt -n
     1  # lpdump : Wed Jun 10 03:41:57 2020
     2  # --- IDm info (FeliCa) ---
     3  # Manufacture Date = 2063/10/8
     4  #               SN = 6981
     5  # Manufacture Code = 2E01
     6  #      Equip. Code = C748
     7    system num 1
     8  # FELICA SYSTEM_CODE = 93B1
     9  # card IDm = 012E48C7CD98451B
    10  # card PMm = 033242828247AAFF
    11  # area num = 4
    12  # service num = 6
    13  # AREA #0 = 0000 (00000)
    14  # AREA #1 = 0040 (00000)
    15  # AREA #2 = 0080 (00000)
    16  # AREA #3 = 00C0 (00000)
    17  # 0040:0008 Ramdom Access R/W          (PROTECTED) 
    18  # 0040:000b Random Access Read only   
    19    0040:0000:3031xxxx30xxxx39xx312020202020
    20  # 0080:0008 Ramdom Access R/W          (PROTECTED) 
    21  # 0080:000b Random Access Read only   
    22    0080:0000:3031xxxx30xxxx39xx312020202020
    23  # 00C0:0008 Ramdom Access R/W          (PROTECTED) 
    24  # 00C0:000b Random Access Read only   
    25    00C0:0000:3031xxxx30xxxx39xx312020202020

xになっているところは隠している場所です。

中身をみてみる

ドキュメントを見ながら、dumpした中身を読んでいきます。

日本語のドキュメントなんで比較的理解しやすかったです。

大事そうなところだけ書いていきます。


* 7行目 system num 1

このカードにシステムがいくつかあるか示しています。

システムとは、論理的なカードの単位を表します。

1枚のカードにSuica楽天Edyの機能を持たせることができるということだと思います。

スマートフォンを読み込んでみるとこの値が3つになっていました。


* 8行目 FELICA SYSTEM_CODE = 93B1

リーダ/ライタがカードを特定するために使われます。 ちなみに、Suica0003です。


* 9行目 card IDm = 012E48C7CD98451B

カードを識別するためのIDです。

基本的にはユニークな番号が付与されます。

簡易的な入室管理システムなどでは、この値を利用して作られいます。


* 10行目 card PMm = 033242828247AAFF

カードの種別および性能を識別するためのパラメータです。

上位2バイトを ICコード、続く6バイトを 最大応答時間パラメータ と呼びます。

ICコードは、ROM種別とIC種別の1バイトずつに分けられています。

このカードのIC種別は32です。

このサイト で確認するとRC-SA00/1で、DESとAES暗号に対応したチップが搭載されていてセキュリティが高いカードらしいです。


19行目 0040:0000:3031xxxx30xxxx39xx312020202020

ここの学籍番号の情報があり、ASCIIでデータが格納されています。

20になっている部分を除き、変換してみるとこんな感じになりました。

017xxxxx01

下位の01は学籍番号に関係ないので、どういう意味の数字かは不明です。

ほかの人の学生証と比べればなにか分かるかもしれません。

あと、学籍番号がこのブロックを含めて、合計3ブロックにあるのも謎です。

最後に

今度はs380をでPython3に対応したらしいnfcpyを使ってみたいと思います。 余裕があれば、学生証を利用した入室管理システムも作るかもしれないです。

参考文献

raspberrypiでFeliCaのIDmを除く(pasori RC-S320版)

[PASMO] FeliCa から情報を吸い出してみる - FeliCaの仕様編 [Android][Kotlin]

FeliCaカード ユーザーズマニュアル 抜粋版

FeliCa技術方式の各種コードについて

SAKITOのポイントガチャ自動化

KIT Developer Advent Calendar20日目です。 今は21日目ですが、空いていたので書いてみました。

はじめに

KITランチを無料で食べるためには、毎日SAKITOのガチャを回すことが必要不可欠です!!

そこで今回はPythonを使ってガチャを自動化したいと思います。

あまりよろしくない記事なら消すので言ってください。

準備

$ pip install requests beautifulsoup4

seleniumを使ったほうが、簡単ですがサーバーで動かすことを考えると環境構築するのが面倒くさいので、今回はrequestsとbeautifulsoup4でやることにしました。

ログイン

requestsを使ってログインするには、セッションを保持する必要があるので、requests.Session()を使います。

とりあえず、デベロッパーツールを使ってログイン時にどんなデータ送っているか見てます。

f:id:ta2mi:20191221012925p:plain

ユーザー名とパスワード以外にもトークンをポストすればいけそうなことがわかりました。

それでは、ログインしてみます。

import requests
import time
from bs4 import BeautifulSoup

BASE_URL = 'https://sakito.cirkit.jp/'

session = requests.Session()
login_url = BASE_URL + 'user/sign_in'
# ログインページにGETリクエストしてトークン得る
bs = BeautifulSoup(session.get(login_url).text, 'html.parser')
authenticity_token = bs.find(attrs={'name': 'authenticity_token'}).get('value')

login_data = {
   'authenticity_token': authenticity_token,
   'user[email]': 'メールアドレス',
   'user[password]': 'パスワード',
}
login = session.post(login_url, data=login_data)
print(login.text)

ガチャを引く

ログインできたので、次はガチャを引いていきます。

ガチャを引くボタンのaタグをみるとdata-method="post" href="/user/point"となっています。

method="post"じゃないのでなんかあやしいけどpostで/user/pointにリクエスト投げれば良さそうです。 1

point_gacha_url = BASE_URL + 'user/point'
point = session.post(point_gacha_url)

ですがこのままpostリクエストをなげてもエラーになります。

原因はCSRFトークンがついてないためです。

X-CSRF-Tokenヘッダをつけることで解決できます。

以下が完成したコードになります。

コード書いている途中で間違ってガチャ回してしまったのでちゃんと確認とれてないので動くが心配・・・

import requests
import time
from bs4 import BeautifulSoup

BASE_URL = 'https://sakito.cirkit.jp/'

session = requests.Session()
login_url = BASE_URL + 'user/sign_in'
bs = BeautifulSoup(session.get(login_url).text, 'html.parser')
authenticity_token = bs.find(attrs={'name': 'authenticity_token'}).get('value')

login_data = {
   'authenticity_token': authenticity_token,
   'user[email]': 'メールアドレス',
   'user[password]': 'パスワード',
}

login = session.post(login_url, data=login_data)

time.sleep(2)  # サーバーへの配慮

bs = BeautifulSoup(login.text, 'html.parser')
csrf_token = bs.find(attrs={'name': 'csrf-token'}).get('content')
headers = {'X-CSRF-Token': csrf_token}
point_url = BASE_URL + 'user/point'
point = session.post(point_url, headers=headers)
print(point.text)

これをサーバーにおいてcronで一日一回動かせば、勝手にポイント溜まっていきます!!


  1. SAKITOはたぶんRailsで動いていて、Railsでは実際のリクエストをJavaScriptで投げているっぽいです

LoRaチュートリアル(完)距離を取得して送信する

今回やること

前回でプログラムによる送受信ができたので、今回は超音波距離センサで距離を取得しその距離に応じて受信側のLEDのON・OFFの制御を行います。

距離を取得して送信

使用する超音波距離センサはHC-SR04です。

このサイト参考に距離を取得するプログラムを前回のプログラムに組み込みます。

deviceplus.jp

組み込んだプログラムはこんな感じです。

getDistance 関数で距離を取得して、それを00010001と結合しています。

#include <SoftwareSerial.h>

#define LORA_RECV_RecvData 100
#define ES920LR_RST_PIN 13
#define LORA_RX 2
#define LORA_TX 3
#define ECHO_PIN 8
#define TRIG_PIN 9

String dstId = "00010001";  /*送信相手の番号*/
const int maxSendTimes = 10; /*最大送信回数*/
const int setCmdDelay = 100; /*待機時間*/

SoftwareSerial LoRa_Serial(LORA_RX, LORA_TX);

void setup() {
  pinMode(ES920LR_RST_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(TRIG_PIN, OUTPUT);
  // LoRaを再起動させる
  digitalWrite(ES920LR_RST_PIN, LOW);
  delay(100);
  digitalWrite(ES920LR_RST_PIN, HIGH);
  delay(1500);

  Serial.begin(9600);
  LoRa_Serial.begin(9600);
  delay(3500);
  loraInit();

  String sendData = "";
  for (int i = 1; i <= maxSendTimes; i++) {
    sendData = dstId + getDistance();
    delay(2000);
    Serial.println(sendData);
    LoRa_Serial.println(sendData);
    while (LoRa_Serial.available() > 0) LoRa_Serial.read();
    delay(4000);
  }
  Serial.println("Start Recv");
}

void loop() {
}

void loraInit() {
  Serial.print("Start...");
  // コマンドモード開始
  LoRa_Serial.println("2"); clearBuffer();
  // bw(帯域幅の設定)
  LoRa_Serial.println("bw 4"); clearBuffer();
  // sf(拡散率の設定)
  LoRa_Serial.println("sf 12"); clearBuffer();
  LoRa_Serial.println("channel 1"); clearBuffer();
  // 自分が参加するPANネットワークアドレスの設定
  LoRa_Serial.println("panid 0001"); clearBuffer();
  // 自分のノードIDを設定
  LoRa_Serial.println("ownid 0002"); clearBuffer();
  // ack受信の設定
  LoRa_Serial.println("ack 2"); clearBuffer();
  //送信元のID付与設定
  LoRa_Serial.println("o 1"); clearBuffer();
  // RRSIの付与設定
  LoRa_Serial.println("p 2"); clearBuffer();
  // 送信モードを設定
  LoRa_Serial.println("n 2"); clearBuffer();
  // 設定を保存する
  LoRa_Serial.println("w"); clearBuffer();
  // 通信の開始
  LoRa_Serial.println("z"); clearBuffer();
  Serial.println("Set up OK!");
}

void clearBuffer() {
  delay(setCmdDelay);
  while (LoRa_Serial.available() > 0) LoRa_Serial.read();
}

int getDistance() {
  double duration;
  double distance;
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH); //超音波を出力
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  duration = pulseIn(ECHO_PIN, HIGH); //センサからの入力
  if (duration > 0) {
    duration = duration / 2; //往復距離を半分にする
    distance = duration * 340 * 100 / 1000000; // 音速を340m/sに設定
    return (int)distance;
  } else {
    return 0;
  }
}

距離に応じてLEDを制御する(受信側)

送信側のプログラムがうまくできていれば、こんな感じのデータが受信できます。

このデータは文字なのでatoiを使って数値に型変換してLEDの制御に用います。

距離が30cm以上だったときにLEDがONになり、それ以下だったらOFFになります。

プログラムはこんな感じです。

#include <SoftwareSerial.h>
#include <stdlib.h>

#define RECV_SIZE 30
#define ES920LR_RST_PIN 10
#define LORA_RX 2
#define LORA_TX 3
#define LED_PIN 12

String dstId = "00010001";  /*送信相手の番号*/
const int maxSendTimes = 10; /*最大送信回数*/
const int setCmdDelay = 100; /*待機時間*/

SoftwareSerial LoRa_Serial(LORA_RX, LORA_TX);

void setup() {
  pinMode(ES920LR_RST_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(ES920LR_RST_PIN, LOW);
  delay(100);
  digitalWrite(ES920LR_RST_PIN, HIGH);  // LoRaを再起動させる
  Serial.begin(9600);
  LoRa_Serial.begin(9600);
  delay(3500);
  loraInit();
  Serial.println("Start Recv");
}


void loop() {
  char RecvData[RECV_SIZE] = "";
  unsigned char n = 0;
  int distance;
  while (LoRa_Serial.available() > 0) {
    RecvData[n] = LoRa_Serial.read();

    // 改行文字が来たらNULL文字にする
    if (RecvData[n] == '\r' || RecvData[n] == '\n') {
      RecvData[n] = '\0';
      clearBuffer();
      Serial.println(RecvData);
      distance = atoi(RecvData);
      if(distance >= 30) {
        digitalWrite(LED_PIN, HIGH);
      } else {
        digitalWrite(LED_PIN, LOW);
      }
      break;
    }
    
    if (n < RECV_SIZE) {
      n++;
    } else {
      n = 0;
    }
  }
  delay(300);
}


void loraInit() {
  Serial.print("Start...");
  // コマンドモード開始
  LoRa_Serial.println("2"); clearBuffer();
  // bw(帯域幅の設定)
  LoRa_Serial.println("bw 4"); clearBuffer();
  // sf(拡散率の設定)
  LoRa_Serial.println("sf 12"); clearBuffer();
  LoRa_Serial.println("channel 1"); clearBuffer();
  // 自分が参加するPANネットワークアドレスの設定
  LoRa_Serial.println("panid 0001"); clearBuffer();
  // 自分のノードIDを設定
  LoRa_Serial.println("ownid 0001"); clearBuffer();
  // ack受信の設定
  LoRa_Serial.println("ack 2"); clearBuffer();
  //送信元のID付与設定
  LoRa_Serial.println("o 2"); clearBuffer();
  // RRSIの付与設定
  LoRa_Serial.println("p 2"); clearBuffer();
  // 送信モードを設定
  LoRa_Serial.println("n 2"); clearBuffer();
  // 設定を保存する
  LoRa_Serial.println("w"); clearBuffer();
  // 通信の開始
  LoRa_Serial.println("z"); clearBuffer();
  Serial.println("Set up OK!");
}

void clearBuffer() {
  delay(setCmdDelay);
  while (LoRa_Serial.available() > 0) LoRa_Serial.read();
}

動作確認

30cm以下の時はLEDがOFFになる

30cm以上の時はLEDがONになる

わかりにくいですが光ってます