· 8 min read

How to Read Suica Information with CoreNFC (FeliCa)

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Preface

Starting with iOS 11.0, iOS devices could read and write NFC tags using CoreNFC, but reading IC cards was not supported. IC cards could only be read by iOS devices starting from iOS 13.

I’ve been interested in NFC for a long time and wanted to try reading data from a Suica (a Japanese transit IC card) myself so that I could check my balance directly on my phone. (I know there are already several apps that do this, but I wanted to build it for learning purposes.)

There are relatively few resources in Chinese or English about Suica online, so I spent a few days researching the FeliCa documentation and implemented it with CoreNFC.

This article will start with the NFC protocol, then cover FeliCa—which is widely used in Japanese transit IC cards—and finally walk through the implementation in Swift.

Since I’ve only just started learning Swift, there might be some areas that aren’t written in the best way. Feel free to ping me on Twitter.

What is NFC?

NFC (Near Field Communication) is a communication protocol, often categorized as a type of RFID wireless communication technology.

This protocol mainly defines:

  • Communication protocol: how the sender and receiver communicate
  • Data exchange: how data is exchanged between the sender and receiver

While it is possible to write data to a card (if you have the key), this article will focus solely on how to read data from FeliCa.

The Problems Solved by NFC

In wireless communication, we can use technologies like Bluetooth, Wi-Fi, etc., but the biggest challenges revolve around security and pairing.

Bluetooth requires devices to pair before communicating, which takes time and makes communication more complex.

If a reader could detect card information from 20 meters away and initiate a payment, that would pose a huge security risk.

Therefore, in NFC, the operating distance is typically within a few centimeters—not only ensuring security, but also reducing noise and interference.

FeliCa

FeliCa is a contactless IC card technology developed by Sony in 2001. Compared with NFC Type-A and Type-B, it is much faster. The reason for its speed might well be the massive rush-hour commuter traffic in Japan.

Taiwan’s EasyCard is an IC card based on Mifare, designed by Philips. Mifare is also a contactless IC card standard widely used around the world.

Currently, it seems that FeliCa is the predominant standard used in Japan.

FeliCa is truly fast. If you’ve taken public transit in Japan, you probably noticed that you don’t even need to slow down your stride when tapping through ticket gates.

Currently, widely used transit IC cards such as Suica, ICOCA, Hayakaken (はやかけん), etc., all use FeliCa technology.

Beyond transportation, transit IC cards can also be used for payments at convenience stores and vending machines.

Data Security

With NFC, some data is openly readable, some is not, and certain cards require decryption with the correct key to read or write data.

Although Android and iOS devices can now read NFC cards, modifying sensitive data like balances still requires the appropriate cryptographic keys.

FeliCa Architecture

FeliCa’s architecture is divided into two major areas: the Private Area (プライベート) and the Common Area.

The Common Area stores readable information, whereas the Private Area holds sensitive data such as personal details or balance management, which requires encryption and decryption to access.

Within the Common Area, it is further divided into several components:

  • System: Represents the overall system of the card. Each card has a 2-byte system code. According to JIS X 6319-4, this value falls between FE00 and AA00. For cards like Suica, this is 0003 (this value is important; we’ll use it later).
  • Area: Contains information such as storage capacity, the number of blocks allocated to each service, and so on.
  • Service: Stores data in blocks for external access. Each service has a 2-byte service code (for example, the transit history service code is 090f). Services are categorized into Random Service, Cyclic Service, and Purse/Pass Service.
  • Block: The basic storage unit where data resides. Each block is 16 bytes, and the number of blocks varies depending on the service.

If you only want to read data from the card, the two key concepts you need to deal with are service and block.

Service Types

As mentioned earlier, services are categorized into Random, Cyclic, and Purse/Pass services, distinguished mainly by how data is accessed and managed:

  • Random Service: Data that can be read and written arbitrarily, determined by the issuer/vendor.
  • Cyclic Service: Used to store log-like sequential records, such as ride history.
  • Pass Service: Used for managing values such as card balances and deductions.

Commands

FeliCa defines various commands in its specification, which can be found in the [FeliCa document](http://www.proxmark.org/files/Documents/13.56 MHz - Felica/card_usersmanual_2.0.pdf). To read FeliCa data, you only need a few commands:

  • Polling
  • Request Service
  • Read Without Encryption

Each command has a corresponding request packet specifying what parameters should be sent. CoreNFC already provides high-level APIs for these commands, so we don’t need to construct raw packets manually.

FeliCa Communication Flow

  1. Detect the card using the Polling command.
  2. Send the Request Service command with a list of service codes to verify whether the services exist and are readable. If a service does not exist or an error occurs, it returns 0xFFFF.
  3. Use the Read Without Encryption command and specify the service codes to retrieve the corresponding block data (up to 16 blocks/services at a time).
    1. This command returns status1 and status2. If both are 0, the read succeeded.
    2. If non-zero, it indicates an error. You can look up the error code definitions in the FeliCa documentation.

iOS Implementation

You can refer to Apple’s sample code to see how to read NFC tags in general; here, we will focus specifically on how to read FeliCaTag.

To test NFC, you must use a physical iPhone—NFC reading cannot be simulated in the iOS Simulator.

You can check NFCTagReaderSession.isReadingAvailable to determine whether the device supports NFC reading.

5]) >> 5 & 0b1111 // get month bit let date = data[5] & 0b11111 // get date bit

let formatter = DateFormatter() formatter.dateFormat = “yyyy-MM-dd” formatter.locale = Locale(identifier: “en_US_POSIX”) return formatter.date(from: “20(year)-(month)-(date)”)!


## Demo

You can find the full source code on [GitHub](https://github.com/kjj6198/nfc-reader).

## Thoughts

Although I was able to read the information successfully, some values turned out different from what I expected.

For example, I searched online for a long time but couldn't find station code mappings, and I'm not even entirely sure if I parsed the station codes correctly.

Also, while the documentation indicates that ride history can store up to 20 blocks, reading all 20 blocks causes failures on certain cards; a safer limit seems to be around 10 blocks (still testing this).

Looking at the data stored inside (my card is a commuter pass), it seems not every entry and exit is recorded. If you were hoping to use the card to track your trip history for something like data visualization, it might not be as straightforward as you'd think.

When it comes to reading FeliCa on iOS, Japanese developers have built a very handy library called [TRETJapanNFCReader](https://github.com/treastrain/TRETJapanNFCReader). If you'd rather not implement it from scratch, definitely check it out. Beyond Suica, it supports many other Japanese IC cards, and can even read Japanese driver's licenses.

When CoreNFC was introduced, I really wanted to try reading FeliCa cards, but there were very few Chinese resources available online.

There are plenty of Japanese implementations, but looking at them, I could only see things like `serviceCode` and `blockList` without really understanding what they meant. That's why I spent a day digging into the FeliCa specifications to implement it myself.

Type conversions in Swift were also quite tricky—such as converting `Data` to `UInt`. I found the snippet below online; even though I don't fully grasp everything it's doing under the hood XD, at least I know I can convert data using `UInt(bytes: Data)`.

```swift
import Foundation

extension FixedWidthInteger {
    init(bytes: UInt8...) {
        self.init(bytes: bytes)
    }

    init<T: DataProtocol>(bytes: T) {
        let count = bytes.count - 1
        self = bytes.enumerated().reduce(into: 0) { (result, item) in
            result += Self(item.element) << (8 * (count - item.offset))
        }
    }
}

It’s quite a lot of information to take in, isn’t it? I hope this article gave you a clearer understanding of how NFC works, what FeliCa is, and most importantly, how to use it alongside CoreNFC in Swift!

Related Posts

Explore Other Topics