Reading FeliCa RFID with a RC-S620S and Arduino

FeliCa reader project

After some more playing around with RFID tags and trying to read some FeliCa tags with the Proxmark3 it was obvious I needed a reader designed for FeliCa.  FeliCa was designed by SONY and has a very long history of usage starting in the 1990’s and beginning wide use in 2001 with the SUICA prepaid rail card.  It is similar to a ISO/IEC 14443, and is closer to a ISO/IEC 18092 (Near Field Communication, NFC TYPE-F) in its communications but its specification actually follows specification JIS: X6319-4.

The card offers very high level encryption and security, I have not seen a single instance of it being hacked even after all these years (yet).

To read the card and get the card id, called the IDm, I found a RC-S620S FeliCa reader at the local shop and searched around to see if anyone had attempted to connect it up to an Arduino platform.  Not suppressing I found some information on this site below with some sample Arduino code.

https://deviceplus.jp/hobby/entry_f02/

With this information I used the Arduino platform to successfully retrieve the IDm from various FeliCa cards in my possession.  After this, I found out that it is not that difficult to retrieve the current balance of the electronic money on the card as well after running across the following below site.  FeliCa is very commonly used in Japan and this can retrieve the unique 8 byte card id (IDm) and balance information for Suica, Icoca, PiTaPa, nanao, edy, and others.

https://www.orsx.net/archives/3835

I then proceeded to create a reader with an OLED display to display the IDm of the card along with the remaining electronic money balance.  This uses an Arduino Feather M0 basic along with a 128×64 OLED display to show the results.  Surprisingly, SONY released an Arduino library for the RC-S620S, which is still up on their website for download, but needs a few fixes since it is from so many years back (2000).

http://blog.felicalauncher.com/sdk_for_air/?page_id=2699

Download the above library and place it in the Arduino “libraries” folder.  In the file “RCS620S.cpp” the following changes need to be made.

Line Search for Replace with
10 #include “Wprogram.h” #include “Arduino.h”
312 Serial.write(data, len); Serial1.write(data, len);
327 if (Serial.available() > 0) { if (Serial1.available() > 0) {
328 data[nread] = Serial.read(); data[nread] = Serial1.read();
338 Serial.flush(); Serial1.flush();

The following hardware items were used in this project:

Adafruit Feather M0 Basic Proto – ATSAMD21 Cortex M0
+ https://www.adafruit.com/product/2772
Adafruit FeatherWing OLED – 128×64 OLED Add-on For Feather
+ https://www.adafruit.com/product/4650
FeatherWing Tripler Mini Kit – Prototyping Add-on For Feathers
+ https://www.adafruit.com/product/3417
NFC FeliCa Reader/Writer Module RC-S620/S
+ https://international.switch-science.com/catalog/353/
FeliCa RC-S620/S and FeliCa Link RC-S730 pitch converter board set (with flat cable)
+ https://international.switch-science.com/catalog/1029/

Here is the pinout of the RC-S602S.  It can work on either 3.3V or 5.0V.  Connect to the “uart1” of the Arduino device being used.  In the case of the Adafruit Feather m0 Basic this is D0 (UART1 RX) and D1 (UART1 TD).  There are two grounds, connect them both to ground.  The RC-S620S can be powered by 5.0v or 3.3v.  An Arduino Uno would work, but it has only one hardware UART, the softserial won’t be able to keep up with the 115200 BAUD of the RC-S620S.  I believe the limit for the softserial is around 9600 or 19200.

Connect the flat cable as shown here in the picture.

RC-S620S flat cable

RC-S620S

RC-S620S pinout

pin color description
1 red VCC (5v or 3.3v)
2 green RXD
3 white TXD
4 black GND
5 n/c do not connect, reserved
6 black GND

A version with the OLED display functions is in the below ZIP file, it is rather big to copy paste here.  Credit for the parts that I copy pasted is these two sources below.

https://deviceplus.jp/hobby/entry_f02/
https://www.orsx.net/archives/3835

https://www.drassal.net/filestore/m0_felica_oled_20210209.zip

The following code is a very simple serial only version that can be pasted.  A zip fie is also available to download.

https://www.drassal.net/filestore/m0_felica_uart_20210209.zip

#include <RCS620S.h>

#define COMMAND_TIMEOUT               400
#define POLLING_INTERVAL              500
#define RCS620S_MAX_CARD_RESPONSE_LEN 30

// FeliCa Service/System Code
#define CYBERNE_SYSTEM_CODE           0x0003
#define COMMON_SYSTEM_CODE            0xFE00
#define PASSNET_SERVICE_CODE          0x090F
#define EDY_SERVICE_CODE              0x170F
#define NANACO_SERVICE_CODE           0x564F
#define WAON_SERVICE_CODE             0x680B

RCS620S rcs620s;

// UNO
#define SERIAL_DEBUG Serial
// Due
// #define SERIAL_DEBUG SerialUSB

void setup() {
  int ret;
  while (!Serial && millis() < 10000);
  Serial.begin(115200);
  Serial.println("Serial init done!");

  // RC-S620Sの初期化
  Serial1.begin(115200);
  Serial.println("Serial1 init done!");
  ret = rcs620s.initDevice();
  while (!ret) {}
  rcs620s.timeout = COMMAND_TIMEOUT;
  Serial.println("RC-S620S init ok");
}

void polling(void) {
  int ret;
  char temp[3];
  char cardid[17];

  // FeliCaのタッチ状態を得る
  ret = rcs620s.polling();

  // FeliCaがタッチされた場合
  if(ret) {
    // IDmを取得する
    for(int i = 0; i < 8; i++){
      sprintf(temp, "%02X", rcs620s.idm[i]);
      cardid[i * 2] = temp[0];
      cardid[(i * 2) + 1] = temp[1];
    }
    cardid[16] = 0;

    printId(cardid);
  }
  
  rcs620s.rfOff();
}

void polling_balance(void) {
  char temp[3];
  char cardid[17];
  uint32_t balance;
  uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN];
   
  rcs620s.timeout = COMMAND_TIMEOUT;
   
  // サイバネ領域
  if(rcs620s.polling(CYBERNE_SYSTEM_CODE)){
    // Suica PASMO
    if(requestService(PASSNET_SERVICE_CODE)){
      if(readEncryption(PASSNET_SERVICE_CODE, 0, buf)){
        // Little Endianで入っているPASSNETの残高を取り出す
        balance = buf[23];                  // 11 byte目
        balance = (balance << 8) + buf[22]; // 10 byte目

        // IDmを取得する
        for(int i = 0; i < 8; i++){
          sprintf(temp, "%02X", rcs620s.idm[i]);
          cardid[i * 2] = temp[0];
          cardid[(i * 2) + 1] = temp[1];
        }
        cardid[16] = 0;

        // 残高表示
        printBalance(cardid, "PASSNET", &balance);
      }
    }
  }
   
  // 共通領域
  else if(rcs620s.polling(COMMON_SYSTEM_CODE)){
    // Edy
    if(requestService(EDY_SERVICE_CODE)){
      if(readEncryption(EDY_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているEdyの残高を取り出す
        balance = buf[26];                  // 14 byte目
        balance = (balance << 8) + buf[27]; // 15 byte目

        // IDmを取得する
        for(int i = 0; i < 8; i++){
          sprintf(temp, "%02X", rcs620s.idm[i]);
          cardid[i * 2] = temp[0];
          cardid[(i * 2) + 1] = temp[1];
        }
        cardid[16] = 0;

        // 残高表示
        printBalance(cardid, "Edy", &balance);
      }
    }
     
    // nanaco
    else if(requestService(NANACO_SERVICE_CODE)){
      if(readEncryption(NANACO_SERVICE_CODE, 0, buf)){
        // Big Endianで入っているNanacoの残高を取り出す
        balance = buf[17];                  // 5 byte目
        balance = (balance << 8) + buf[18]; // 6 byte目
        balance = (balance << 8) + buf[19]; // 7 byte目
        balance = (balance << 8) + buf[20]; // 8 byte目

        // IDmを取得する
        for(int i = 0; i < 8; i++){
          sprintf(temp, "%02X", rcs620s.idm[i]);
          cardid[i * 2] = temp[0];
          cardid[(i * 2) + 1] = temp[1];
        }
        cardid[16] = 0;
        
        // 残高表示
        printBalance(cardid, "nanaco", &balance);
      }
    }
     
    // waon
    else if(requestService(WAON_SERVICE_CODE)){
      if(readEncryption(WAON_SERVICE_CODE, 1, buf)){
        // Big Endianで入っているWaonの残高を取り出す
        balance = buf[17];                  // 21 byte目
        balance = (balance << 8) + buf[18]; // 22 byte目
        balance = (balance << 8) + buf[19]; // 23 byte目 balance = balance & 0x7FFFE0; // 残高18bit分のみ論理積で取り出す balance = balance >> 5;             // 5bit分ビットシフト

        // IDmを取得する
        for(int i = 0; i < 8; i++){ sprintf(temp, "%02X", rcs620s.idm[i]); cardid[i * 2] = temp[0]; cardid[(i * 2) + 1] = temp[1]; } cardid[16] = 0; // 残高表示 printBalance(cardid, "waon", &balance); } } } rcs620s.rfOff(); } // request service int requestService(uint16_t serviceCode){ int ret; uint8_t buf[RCS620S_MAX_CARD_RESPONSE_LEN]; uint8_t responseLen = 0; buf[0] = 0x02; memcpy(buf + 1, rcs620s.idm, 8); buf[9] = 0x01; buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);
 
  ret = rcs620s.cardCommand(buf, 12, buf, &responseLen);
   
  if(!ret || (responseLen != 12) || (buf[0] != 0x03) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0) || ((buf[10] == 0xff) && (buf[11] == 0xff))) {
    return 0;
  }
 
  return 1;
}
 
int readEncryption(uint16_t serviceCode, uint8_t blockNumber, uint8_t *buf){
  int ret;
  uint8_t responseLen = 0;
   
  buf[0] = 0x06;
  memcpy(buf + 1, rcs620s.idm, 8);
  buf[9] = 0x01; // サービス数
  buf[10] = (uint8_t)((serviceCode >> 0) & 0xff);
  buf[11] = (uint8_t)((serviceCode >> 8) & 0xff);
  buf[12] = 0x01; // ブロック数
  buf[13] = 0x80;
  buf[14] = blockNumber;
 
  ret = rcs620s.cardCommand(buf, 15, buf, &responseLen);
 
  if (!ret || (responseLen != 28) || (buf[0] != 0x07) ||
      (memcmp(buf + 1, rcs620s.idm, 8) != 0)) {
    return 0;
  }
 
  return 1;
}

void printId(char *card_id){
  Serial.print("IDm      : ");
  Serial.println(card_id);
  return;
}

void printBalance(char *card_id, char *service_name, uint32_t *balance){
  char result[8];
  Serial.print("IDm      : ");
  Serial.println(card_id);

  Serial.print(" SERVICE : ");
  sprintf(result, "%u", *balance);
  Serial.println(service_name);
  
  Serial.print(" BALANCE : ");
  Serial.println(result);
  
  return;
}

void loop() {
  // put your main code here, to run repeatedly:
  //polling();
  polling_balance();
  delay(POLLING_INTERVAL);
}

Leave a Reply

Your email address will not be published. Required fields are marked *