If you have any questions or feedback, pleasefill out this form
This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.
Preface
iOS 11.0 introduced the ability to read and write NFC Tags through CoreNFC, but it was unable to read information from IC cards. This capability was only added starting with iOS 13.
I have been fascinated by NFC technology for quite some time, and I wanted to explore the possibility of reading data from Suica (a Japanese transportation IC card) myself, allowing me to check the balance directly on my mobile phone. While I am aware that several apps already provide this functionality, my goal is to do it for educational purposes.
There are relatively few resources available in Chinese and English about Suica on the Internet, so I dedicated a few days to studying the FeliCa documentation and implemented it using CoreNFC
.
This article will begin with an overview of the NFC protocol, then delve into FeliCa
, which is widely used in Japanese transportation IC cards, and finally, I will provide the implementation in Swift.
As I am just starting to learn Swift, there may be parts that are not well articulated; feel free to reach out to me on Twitter.
What is NFC?
NFC (Near Field Communication) is a communication protocol, often referred to as RFID wireless communication technology.
The protocol primarily defines the following:
- Communication Agreement: outlines how the sender and receiver communicate
- Data Exchange: details 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 Bluetooth, Wi-Fi, etc., to establish connections, but the primary concerns are security
and matching
.
Bluetooth requires pairing before devices can communicate, which can be time-consuming and complicates the process.
If a reader could detect card information from 20 meters away and directly initiate a payment, it would lead to significant security issues.
Thus, NFC is designed for short detection distances, usually within a few centimeters, to enhance security and minimize noise interference.
FeliCa
FeliCa is an IC card technology developed by Sony
in 2001. Compared to NFC Type-A and Type-B, it operates at a faster speed. Its speed may be a response to the heavy commuter traffic in Japan.
In Taiwan, the Youyou card is an IC card created by Mifare, designed by Philips. Mifare is also a widely used contactless IC card technology around the world.
Currently, FeliCa appears to be the predominant technology used in Japan.
FeliCa
is incredibly fast. If you use public transportation in Japan, you may have noticed that you don't even need to pause when entering the platform.
Today, it is widely used in transportation IC cards such as Suica, ICOCA, and は や か け ん, all of which utilize FeliCa
technology.
Beyond transportation, you can also use your transportation IC card for payments at convenience stores.
Data Security
In NFC, certain data can be read, while others cannot, and some cards require decryption with the correct key for reading and writing data.
Although both Android and iOS can now read NFC cards, modifying balances and other data requires the correct key.
FeliCa Architecture
The architecture of FeliCa can be divided into two main parts: the private domain (プ ラ イ ベ ー ト) and the common domain.
The common domain holds information that can be read, while the private domain contains sensitive data, such as personal information or balance controls, which must be encrypted and decrypted for access.
In the common domain, it can be further subdivided into several sections:
- System: indicates the unit of the card. Each card is assigned a system code (2 bytes). According to the JIS 6319-4 standard, this value ranges from FE00 to AA00. For example, Suica holds the system code
0003
(this value is significant, and we will reference it later). - Area: Contains details like storage capacity and the number of blocks available for each service.
- Service: Stores data in blocks for external access. Each service is assigned a service code (2 bytes), such as the entry record service code
090f
. Services can be categorized as random service, cyclic service, and pass service. - Block: The actual storage space for data, with each block being 16 bytes. The number of required blocks varies depending on the service.
If you only want to read the information stored on the card, you'll need to focus on two components: service
and block
.
Service Type
As mentioned earlier, services are categorized into random, cyclic, and pass types, primarily distinguished by the way data is accessed.
- Random Service: Data that can be freely read and written, as determined by the manufacturer.
- Cyclic Service: A location for storing records, such as historical data.
- Pass Service: A section for managing balances and deductions.
Command
There are various commands in [FeliCa](http://www.proxmark.org/files/Documents/13.56 MHz - Felica/card_usersmanual_2.0.pdf), which can be found in the [FeliCa documentation](http://www.proxmark.org/files/Documents/13.56 MHz - Felica/card_usersmanual_2.0.pdf). To read FeliCa data, you need several commands:
- Polling
- Request Service
- Read Without Encryption
Each command has a corresponding request packet that specifies the contents of the request. This part already has corresponding functions provided in CoreNFC
, so we don’t need to worry about that.
Communication Process in FeliCa
- Capture cards using the polling command.
- Use the Request Service command, providing the service code list. The card will verify whether the service code is valid or accessible. If the service does not exist or there is an error, it will return
0xFFFF
. - Employ the read without encryption command and pass in the service code to retrieve the corresponding data in blocks (up to 16 services).
- This command will return status1 and status2. If both are 0, there are no issues.
- If either is not 0, it indicates an error. You can consult the FeliCa documentation for the error code status.
iOS Implementation
You can refer to the examples provided by Apple Developer to learn how to read NFC Tags; here, we will concentrate on how to read FeliCaTag
.
To implement NFC, you must have a physical iPhone, as NFC cannot be read through the simulator.
You can use NFCTagReaderSession.isReadingAvailable
to check if your device can read NFC data.
let year = data[4] >> 5 // get year bit
let month = data[4] >> 1 & 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 view the source code on GitHub.
Thoughts
Although I successfully read the information, some numbers differed from my expectations.
For instance, I couldn't find the station code after extensive searching online, and I'm uncertain if I parsed the station code correctly.
While it is stated that the history record can encompass 20 blocks, some cards fail to read this way; a safer range appears to be 10 blocks (still testing).
Upon examining the records (my card is a standard ticket), it seems that not every entry and exit is recorded. If you plan to use cards to track your own inbound and outbound information, it may be challenging to visualize that data.
Regarding reading FeliCa on iOS, Japanese developers have created a library called TRETJapanNFCReader, which is quite user-friendly. If you want to use it directly, you can refer to it. In addition to SUICA, it supports several types of IC cards, including the ability to read driver's licenses.
When I first encountered the CoreNFC SDK, I was eager to try reading FeliCa information, but there are limited Chinese resources available online.
While there are many Japanese implementations, I could only see the service codes, block lists, and so on without understanding their meanings. So, I spent a day reading the FeliCa documentation and attempting to implement it on my own.
I also faced challenges with the Swift conversion of different types, such as converting Data into UInt, and I found this code online. However, I didn't fully comprehend what it was doing XD; I only know that UInt(bytes: Data)
is the method for conversion.
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 was quite a journey to delve into all of this, wasn't it? I hope you now have a better understanding of how NFC works, what FeliCa is, and, more importantly, how to use it in conjunction with CoreNFC in Swift!
If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨
☕Buy me a coffee