Smart cards and programming (python)

Father

Professional
Messages
2,605
Reputation
4
Reaction score
583
Points
113
This article is the third in a series about using smart cards. The first one — Smart cards and Programming-was written in 2017 and all the examples in it were in C and C++. The second one is Smart cards and programming (java) — in 2019, Java was also used as the basic language. And now it's time for another reworking, this time all using the example of python3, the code on which turns out to be compact, expressive and understandable. In addition to the new language, reviews, comments, and suggestions have accumulated over the years, and experience in using other devices (cryptographic USB tokens, other types of contactless cards) has emerged.All this is reflected in the text, which differs significantly from previous articles in structure and wording.
If you are just getting acquainted with these technologies, I recommend reading this article. I tried to take into account all the shortcomings of the past ones, clarify and clarify the terminology, and also reveal some points in more detail. I also added several new sections that were not included in previous articles: Mifare Ultralight chips, NFC tags and NDEF, direct access to the reader's radio module for executing arbitrary NFC commands, MIR bank cards, and SIM cards.

We all actively use smart cards, even without knowing it, chip bank cards, NFC tags, contactless pass keys, USB key for electronic signature, SIM cards in phones-these are all smart cards. In this very large text, I will talk in detail about the use of smart cards at the application software level. There will be a lot of theory, a lot of references to standards and specifications, and a lot of code. I didn't set out to write an encyclopedia.This format of the article is not enough, but it is quite possible to give an overview of the subject area. At the end, there is a list of books that cover the topic of smart cards in detail and systematically.

I chose Python for its simplicity, accessibility, usability, and rich module library. You don't need any additional knowledge other than Python and the operating system on which you will run the code. Initially, the entire software environment is focused on Unix-like systems — various variants of Linux and macOS, but everything should work in Windows (however, I did not check). All demo programs use the console (terminal), so they should work about the same everywhere, I don't use any GUI libraries, just the command line.

In the text of the article, there is no division into a separate block with theory and examples. Instead, theory and examples alternate, so you will see the first code almost at the very beginning.

Almost all available third-party materials (standards, specifications, books) are written in English, so you need to know it if you want to study the topic more deeply yourself.

If you have any suggestions for content, you can write directly in the comments or directly to my email address: [email protected]

For a complete acquaintance, you will need additional equipment — readers (readers) for smart cards, cryptographic tokens, a variety of smart cards, I will tell you more about this below.

Content
  • What is a smart card?
  • Necessary equipment
  • Software and environment
    • Debian/Ubuntu Linux
    • Mac OS X
    • Source code of the examples
    • Data submission agreements
    • Debugging/logging in Mac OS X
    • Debugging/logging on Linux
  • Theory: software libraries
  • example-01: getting a list of readers
  • example-02: reading a card from a reader
  • Theory: PC/SC architecture
    • Terms, abbreviations, and data types
    • Hierarchical model
    • How IFD and ICC work
    • PCD and PICC operation diagram
    • Data exchange protocols
    • Structure of the command-response pair
  • example-03: Getting the UID of a contactless card
  • Theory: data on a smart card, card types by data management method
  • example-04: determining the type of contactless memory card
  • example-05: An application for interactive work with APDU queries
  • Theory: Mifare Classic 1K/4K Memory Cards
  • example-06: Reading a Mifare Classic 1K/4K Card
  • example-07: Parsing data on a Mifare Classic 1K/4K card
  • example-08: Practical example: using a Mifare Classic 1K/4K card as a payment card
    • issue-card
    • refill-card-balance
    • check-card-balance
    • checkout
    • revoke-card
  • Theory: Mifare Ultralight Cards
  • example-09: Practical example: using the Mifare Ultralight card
    • read-mifare-ultralight
  • Theory: NFC tags
  • Theory: NFC Type 2 Tag
  • example-10: Operations with the NFC Type 2 Tag
    • nfc-type2-read
    • nfc-type2-write
  • Working with contact memory cards
  • example-11: Working with the SLE 5542 pin memory card
    • sle-card-read
    • sle-card-write
  • Theory: Industry standards
  • Theory: interaction with a microprocessor card
    • Working with data
    • Operating system on the card
    • Data: files and apps
  • Theory: encoding of information objects
    • SIMPLE-TLV
    • BER-TLV
  • example-12: Reading ISO / IEC 7816-4 data using a bank card as an example
    • Processing the result of executing a command in the T=0 protocol
    • General principles of working with a bank card
    • Bank Card: App selection
    • Search for ADF via PSE
    • Parsing the FCI template (get_AID_from_PSEFCI)
    • ADF search without PSE
    • Selecting an ADF and parsing its FCI
    • Start of a financial transaction via GET PROCESSING OPTIONS
    • Reading payment app data
  • Theory: ATR
  • example-13: Parsing the ATR
  • Using the ISO/IEC 7816-4 file system as an example of UICC
  • example-14: Reading data from UICC
  • Conclusion
  • Links
    • Datasheets and manuals
    • Standards and specifications
    • Literature
    • Tools, sites, and software

What is a smart card?

Initially, the smart card was a plastic ID-1 card measuring 85.60 × 53.98 mm with rounded corners (a standard bank / credit card has the same shape and dimensions). A microchip is mounted in it, the communication contacts of which are displayed on one side in the form of metal contact pads of a standardized size.

ID-1 cards


Examples of smart cards: a standard SIM card, an NTV-plus client card, and an empty, unpersonalized SLE5542 smart card.

Later, smart cards in the ID-000 format appeared, they were used in mobile phones and are known to us as SIM cards. Until now, mobile operators sell SIM cards in the ID-1 format, from which the ID-000 can be broken out using previously left incisions and inserted into the phone.

Then they invented smart cards without an external contact pad, in which electromagnetic induction is used for power supply and a radio channel is used for communication. Some USB devices also use the API for working with smart cards, such as crypto tokens (Yubikey, eToken, Aladdin, etc.).

A common feature of all smart cards is that they do not have their own power source and rely on an external one.

The main advantage of a smart card is the physical security of the data stored on it. The microchip is very small and all the" equipment " of the microcomputer is placed on it, without output of internal contacts to which it is possible to connect for interception. cards were also designed with other possible attacks in mind: X-ray scanning, micro-grinding, and so on.

The approximate life cycle of a smart card (for example, a bank card with a chip) consists of several stages. It involves the chip manufacturer, smart card manufacturer, customer, and customer:

  1. Manufacturing of the chip / processor. At this stage, the direct manufacturer of the chip, after physical production, also writes to it the "firmware"provided by the smart card manufacturer, which is universal and identical for all cards of the batch.
  2. Initial initialization of the smart card. A batch of chips is sent to the card manufacturer, who modifies the firmware in the necessary way, for example, prescribes a unique serial number inside each card, and then disables the ability to change it with a special API request.
  3. Making a smart card. The manufacturer inserts the chip into cards of the desired format and sends them to the customer, for example, a bank.
  4. Personalization. The customer, using the methods of flashed software on the card, writes their applications to it, for example, banking; as well as additional data, for example, the client's name, account number, and so on. After that, it finalizes the card with a special request, and then restricts recording new applications, for example.
  5. Extradition. The card is given to the client and he uses it for its intended purpose.
  6. Recycling. The card is discarded or destroyed.
The main standard for smart cards is ISO / IEC 7816, it covers all aspects of the device, communication and operation of cards with a contact pad (contact cards).

For contactless smart cards, the ISO/IEC 14443 standard is used, and it describes details specific to devices with a radio interface. Later, it became part of a broader group, which also includes standards for NFC.

There are several other standards, some of which I will discuss later. I will immediately note that I will not talk about the physical and low-level details of the functioning of smart cards, we are only interested in relatively high-level operations.

Необходимое оборудование

Статья полностью посвящена работе со смарт-картами на компьютере (десктопе или сервере). Для работы с бесконтактными картами (RFID/NFC) я использую популярное, недорогое и распространённое устройство-считыватель ACS ACR122U, оно подключается по USB, имеет драйвера для всех операционных систем и позволяет выполнять многие NFC-операции. Его легко купить как в России (например, на озоне он в марте 2023 года он стоил около 3000 рублей), так и заказать из Китая (например, через Aliexpress).

ACS ACR122U-A9


Contactless card reader ACS ACR122U-A9.

If you have a different reader for contactless cards, some examples may not work, because working with such readers in our software environment takes place through the translation of contactless protocol commands into PC/SC commands.

❈ ❈ ❈

For using smart cards with a contact pad (contact smart cards) I bought another device from the same manufacturer — the ACS ACR38U, which is also a very popular and inexpensive reader and also connects via USB. It can also be easily purchased through ozon or aliexpress.

ACS ACR38U-I1


Contact card reader ACS ACR38U-I1.

Drivers for these devices can be downloaded from the manufacturer's official website:

However, the drivers for both devices are the same, just download and install any of the packages. In Linux and Macos, both devices already work "out of the box" without the need to install additional libraries.

Later in the text, I will call all devices for connecting smart cards readers, this is already a well-established term in Russian, almost a literal translation of the English-language name-reader. Sometimes they are also called terminals, but in this article I will use only the word reader, so that there is no confusion with the terminal-the console of the operating system or the terminal as a payment device.

We will also need several different smart cards for experiments. For example:

  • bank chip cards, including NFC cards (MasterCard PayPass, Visa payWave);
  • standard white blank contactless card from a typical ACR122U kit on aliexpress ("Factory NFC card", Mifare Classic 1K, Mifare Classic 4K);
  • standard white blank contact card (for example, SLE5542 or SLE5528);
  • Universal Electronic Card (UEC, at least for something useful!);
  • Troika Moscow transport card;
  • Novosibirsk Unified Transport card;
  • or a transport card of your city;
  • Russian biometric passport (it has an NFC chip);
  • USB tokens (for example, yubikey or Rutoken);
  • NFC tags;
  • Satellite TV CAM card (NTV-plus, Tricolor);
  • SIM card of any mobile operator (to use it in a standard reader, you will need to insert it back into the slot of the original ID-1 format card and secure it with tape);
  • authentication card for euro-tachograph;
  • a smartphone with NFC support.
There are other contactless cards in the same ID-1 version, for example, passes for office turnstiles (HID, EM Marin, EM4100), but they are made according to a completely different standard, work at other frequencies and are not considered in this article. I have a review article including this format — Electronic keys and cards for access control systems.
For experiments with recording cards, I recommend buying a set of several Mifare Classic 1K, Mifare Ultralight, NTAG 213, NTAG 215 cards at once, they are relatively inexpensive and are sold on ozon or aliexpress.

❈ ❈ ❈

I also strongly recommend installing the NFC Tools app on your android smartphone. It is a very useful tool for studying NFC devices (cards, chips, tags): reading, writing, and executing arbitrary commands.

Software and environment

The basic operating system for experimentation is Mac OS X or Debian / Ubuntu, but any other linux distribution will do. The programming language is Python 3 (the code is written and tested in Python version 3.10). All examples are cross-platform and should run on linux (debian/ubuntu) and Mac OS X without modifications. I didn't check its performance on Windows, but it should work there too.

Debian/Ubuntu Linux

This article actively uses the console (aka shell session, aka terminal) to execute commands, so text like "run the following command" should be read as "run the following command in the console/terminal". Listings use the percent symbol (%) at the beginning of each command to indicate a particular command.It represents the command line and does not need to be copied to the terminal when executing commands.
Sometimes listings contain the expected result of a command along with its execution, and it is displayed in a gray font.
All necessary packages are installed from the standard repository:

% sudo apt install pcscd python3 python3-pyscard
After installation, the pcscd service should start automatically, but on some systems this does not happen, so just in case, run two more commands:

% sudo systemctl enable pcscd
% sudo systemctl enable pcscd.socket

Mac OS X

All the code has been tested on the latest version of macOS 13.2 Ventura, and the standard Python interpreter is used. However, we need non-standard libraries to work, which we will put in a separate virtual environment so as not to interfere with other python programs.

But first you need to install XCode tools for the command line, this is done with this command:

% xcode-select --install
Next, you need to initialize the virtual environment. This operation must be performed only once. Run the following commands:

% python3 -m venv ~/.venv-pyscard
% source ~/.venv-pyscard/bin/activate
% pip install wheel swig
% pip install pyscard ndeflib
% deactivate
With these commands, we initialize the virtual environment in the directory ~/.venv-pyscard, activate it for the current console session with the commandsource ~/.venv-pyscard/bin/activate, and install the necessary packages for operation. At the end, we deactivate the environment with the command deactivate.

Important note
I assume that you are already familiar with the venv module and how the python virtual environment works, so I won't talk about this in detail. Just note that to run the examples, you need to activate this virtual environment every time in the console session with the command source ~/.venv-pyscard/bin/activate. Please do not forget about this. In the future, I assume that all commands are run in this activated virtual environment.

Source code of the examples

The source code for all the examples is in my github repository, which was created specifically for this article: https://github.com/sigsergv/pcsc-tutorial-python. I recommend this mode of working with text:

Even if you don't have a hardware reader, I recommend reading the sections with relevant examples anyway.

The code is written exclusively for educational and demonstration purposes and does not meet the criteria of industrial quality. For example, there are very few checks on input data and program arguments.

The repository is divided into directories with names example-01, example-02and so on. Each of them contains at least one program as a file without an extension, for example, list-readers. Each section with an example must contain a link to the full source code of the program, since the text will usually contain only an important, essential piece of code for brevity.

Data submission agreements

We will work a lot with binary (binary) data, which we will use the default hexadecimal (also known as hex) format to represent. We will write the data segment as a list of hexadecimal values-octets (8 bits long), supplemented with zero if necessary and optionally separated by spaces. Octets will also be called bytes.

When we are talking about a separate octet value, we will write it in classical notation with a prefix0x, for example, 0x9Aor 0x08. If there is no suffix, then the decimal representation is assumed (for example, 0x9Aequals154).

⭐ Many specifications and standards use a notation with a suffix hfor hexadecimal values and a suffix dfor decimal values. In this notation, values are written as 9Ahand 08h. I will not use this notation in my article.
In the code, we will actively use the string representation of data segments for readability, written in hex format, for example, FF 00 00 00 02. Segments presented in this form are converted to byte lists for real use. These are traditional lists consisting of bytes, and not the built-in bytes or bytearray types.

Debugging/logging in Mac OS X

OS X 10.10 Yosemite introduces a standard tool for monitoring the activity of smart/nfc card readers. However, it is activated and used differently in different versions.

For the version of macOS 10.12 Sierra and up to the latest at the time of writing macOS 13.2 Ventura, the sequence for enabling logging is as follows::

  • disconnect the reader from the USB port;
  • enabling logging with the command:
    % sudo defaults write /Library/Preferences/com.apple.security.smartcard Logging -bool true
  • connecting the reader again;
  • we look at the logs in the shell session with the command:
    % log stream --style compact --predicate 'process == "com.apple.ifdreader"'

    or for a very compact output:
    % log stream --style compact --predicate 'process == "com.apple.ifdreader"'|sed -E -e 's/ com.apple.+APDULog]//'
Logging mode is turned off automatically when the device is disconnected from the USB port. Logging is disabled manually with the command:

% sudo defaults write /Library/Preferences/com.apple.security.smartcard Logging -bool true
It is very convenient to open the log in a separate window of the shell session and leave it there to work.

❈ ❈ ❈

In OS X 10.10 Yosemite and OS X 10.11 El Capitan, the sequence is different.

Logging is enabled with this command before connecting the reader to the USB port:

% sudo defaults write /Library/Preferences/com.apple.security.smartcard Logging -bool true
After that, you can view logs from the shell session:

% syslog -w -k Sender com.apple.ifdreader
Mar 10 21:23:03 mba com.apple.ifdreader[64546] <Notice>: card in
Mar 10 21:23:03 mba com.apple.ifdreader[64546] <Notice>: ATR:3b 8f 80 01 80 4f 0c a0 00 00 03 06 03 00 01 00 00 00 00 6a
Mar 10 21:23:04 mba com.apple.ifdreader[64546] <Notice>: card out
Logging mode is turned off automatically when the device is disconnected from the USB port. Logging is disabled manually like this:

% sudo defaults write /Library/Preferences/com.apple.security.smartcard Logging -bool false

Debugging/logging on Linux

First, we stop the pcscd daemon, and then run it in a separate terminal in the mode of displaying sent commands:

% sudo service pcscd stop
% sudo pcscd --apdu --foreground
00000000 APDU: FF CA 00 00 0A
00000591 SW: 9C 0C AA 99 0A 4F 0C A0 00 00 90 00
...
There is a method without stopping the daemon, through the program pcsc-spy(it is included in the packagelibpcsclite-dev), but it does not always work. First, you define an environment variable in one terminal session and then run programs in the same session. This is what it looks like in Debian / Ubuntu on the amd64 architecture:

% export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpcsclite.so
Then in another terminal runpcsc-spy -n > pcsc-dump.log, and then again in the first terminal run the desired program, when it works, you will receive in the file pcsc-dump.logthe full log of interaction with the library.

Theory: software libraries

Only one high-level interface for working with smart cards and NFC devices is actually available to the developer. It is implemented as several system services, and its interface part is the PC/SC framework. PC/SC stands for Personal Computer / Smart Card and was ported from Windows to Linux/macOS with an almost identical API.

PC/SC is a specification proposed by the PC/SC Workgroup, specifically for working with it (and only with it!) this article is dedicated to. Other low-level commands, protocols, and specifications are not considered. Work with NFC also occurs exclusively through the PC/SC layer.

PC / SC greatly simplifies the developer's life precisely due to unification, he does not need to study the low-level driver commands of each device.

❈ ❈ ❈

In Python, we will use the pyscard module, which provides a high-level interface to the PC/SC system library. This module hides a large amount of verbose code that you might have seen in the article about using smart cards in C / C++. The main information block of data in pyscard is a list of bytes, this is a list consisting of bytes. Not a string, not a byte string, not a byte array. The library provides special functions for converting different data types to and from byte lists.

❈ ❈ ❈

The article contains a lot of links to functions, classes, and modules. They look like the 📚 icon and are placed behind the element they belong to. Clicking on this link opens the corresponding section of the API documentation in a new tab.

example-01: getting a list of readers

Source code of the program: example-01/list-readers

The goals of this example are:
  • give a general idea of what it looks like to work with a smart card reader in the program code;
  • use a very simple and compact example to get a tangible result;
  • let's talk about terms and theory later, first — the first working program.

What exactly do we need to do:
  • connect to the PC/SC system library via the pyscard module;
  • get a list of available readers;
  • print it on the screen.
Here is the entire program code:

#!/usr/bin/env python3

from smartcard.System import readers

def main() -> int:
print('List of available readers:')
for i,r in enumerate(readers()):
print('{0}: {1}'.format(i, str(r)))
return 0

if __name__ == '__main__':
main()
To get a list of readers, we use the 📚 function readers() from the module smartcard.System, so you need to explicitly import it, this is done in the line:

from smartcard.System import readers
When called, this function returns a list of available readers along with the index of each reader in the list. Each element of the list is an object of the classsmartcard.pcsc.PCSCReader.PCSCReader 📚, which represents a specific reader. To get its name, we use the standard python method-calling a function str()on an object.

⭐ In fact, each of smartcard.System.readers()the elements returned by the function is an object of a class that implements the underlying abstract class smartcard.reader.Reader.Reader.
If we connect both the previously mentioned ACS ACR122U and ACS ACR38U readers to the computer, the result of running the program will be as follows::

% python3 example-01/list-readers
List of available readers:
0: ACS ACR 38U-CCID
1: ACS ACR122U PICC Interface

example-02: reading a card from a reader

Source code of the program: example-02/reader-read-card
The goals of this example are:

  • connect to a specific reader;
  • if there is no card in it, wait until it appears there;
  • read something from the card.
For simplicity, we will assume that we have only one reader connected, for example, ACS ACR38U for contact cards, and this reader is empty, without a card inserted. And for example, take any of your bank cards with a contact platform.

Here is the entire program code:

#!/usr/bin/env python3

from smartcard.System import readers
from smartcard.CardRequest import CardRequest
from smartcard.util import toHexString

def main() -> int:
reader = readers()[0]
print('Connected reader: {0}'.format(reader))
cardrequest = CardRequest(timeout=None, readers=[reader])
print('Waiting for the card...')
cardservice = cardrequest.waitforcard()
cardservice.connection.connect()
print('Inserted card with ATR: {0}'.format( toHexString(cardservice.connection.getATR())) )
return 0

if __name__ == '__main__':
main()
Here, too, everything is simple. First, we readerget an object in the variable that represents the first reader available:

reader = readers()[0]
Since there is no card in the reader, we need to wait for it to appear. This is done by creating an object of the 📚 class smartcard.CardRequest.CardRequestand then calling the methodwaitforcard(). In the constructor of this class, you can set various criteria for waiting for the card. In our case, we specify the previously received reader, and also determine the timeout during which we will wait, timeout=Nonemeaning that the wait is infinite.

cardrequest = CardRequest(timeout=None, readers=[reader])
print('Waiting for the card...')
cardservice = cardrequest.waitforcard()
cardservice.connection.connect()
When a card is inserted into the specified reader, the method execution waitforcard()is interrupted and the class object is returned to us cardservicesmartcard.CardService.CardService(or an exception is thrown). The object field connectioncontains an object that represents the established connection before the inserted card. The only thing we are guaranteed to be able to read from the card is its ATR. In the next theoretical section, we will explain what it is, but for now it is enough just to know that it is a small set of bytes. Just read it using the method getATR()and print it on the screen using the 📚 functiontoHexString(), which prints the list of bytes in human-readable form.

print('Inserted card with ATR: {0}'.format( toHexString(cardservice.connection.getATR())) )
The result of running the program is as follows:

% python3 example-02/reader-read-card
Connected reader: ACS ACR 38U-CCID
Waiting for the card...
Inserted card with ATR: 3B FF 97 00 00 80 31 FE 45 00 31 C1 73 C8 21 10 64 47 4D 30 30 00 90 00 E6
⭐ The call cardservice.connection.connect()can also pass the specific transport protocol that we want to use for the connection (T=0 orT=1), but we skip this argument, relying on automatic protocol selection. In the future, I will have examples for different protocols and query features in each of them.
We don't know anything about the card's data or functions yet, but they all have a common property — ATR (Answer-To-Reset) is a short (no more than 33) array of bytes that the card must transmit to the reader when connected. If the card doesn't do this in a certain amount of time, it is considered to be functioning incorrectly. The ATR contains basic information about the card and the technical parameters of the connection, but its format is quite complex and depends on the chip manufacturer, the "applications" installed on the card, and so on. The ATR is stored in the reader as long as the card is connected.

The structure of the ATR is defined in Section 8.2 of the ISO / IEC 7816-3 standard, and I will discuss this in more detail later, for now we will limit ourselves to its byte representation.

Important points from this example
  • You must explicitly wait for the reader to be ready and for the card to appear in it.
  • After connecting to the card, all operations with it are performed through an object of the class javax.smartcardio.Card.
  • After finishing working with the card , you must disconnect the disconnect()card. If you do not do this, the reader may "hang" and you will need to physically reconnect it.
  • When the card is connected to the reader, it must immediately transmit its ATR.
  • The ATR is not a card identifier and does not contain the card ID. All cards of the same type can have the same ATR.
  • There is a web service on the Internet that can be used to parse the ATR: https://smartcard-atr.apdu.fr/
  • Experiment with the ATR of different cards or NFC tags.
  • Instead of a card, you can hold a smartphone with NFC enabled to the reader, for example, which will recognize it and the program will show the ATR.
  • A contactless card (NFC) does not have an ATR, since there is a different initialization procedure, but the reader generates the ATR itself for compatibility and gives it to the program.

Theory: PC / SC architecture

For us, the main standard is the official PC/SC Workgroup specification. Its purpose is to describe a single unified interface for working with certain classes of card readers. PC/SC specifications are available for free download on the organization's website: https://pcscworkgroup.com/specifications/, in the text of the article, I will give direct links to the necessary standards.

As of March 2023, the latest version of the specifications is 2.01.14, released in 2013. It consists of ten parts:

PC/SC Workgroup specifications are based on the following international standards::

  • ISO / IEC 7816-a family of standards that define all aspects of working with smart cards with a contact pad;
  • ISO / IEC 14443 is a family of standards that defines all aspects of working with short-range contactless passive devices (up to 10 cm) over a radio channel at a frequency of 13.56 MHz;
  • ISO / IEC 15693 is a family of standards that defines all aspects of working with long-range contactless passive devices (from 10 cm to 1 m) over a radio channel at a frequency of 13.56 MHz;
  • EMV is a family of standards for payment cards.
Some of these standards are available for free download, and they are referenced above.

Some standards have official Russian translations and are available for free (or relatively free) download, with links to them at the end of the article in the Links section.

Terms, abbreviations, and data types

First, a few terms and abbreviations that are actively used in the standards (and then in my text).

ICCIntegrated Circuit Card, Integrated circuit card, "smart card"- a plastic card with a built-in chip and a contact pad.PICCProximity Integrated Circuit Card, Contactless card on an integrated circuit (name according to GOST R ISO/IEC 14443-1) — a plastic card with a built-in chip and a radio interface. In GOST 14443-1, the following is written: A contactless card on an integrated circuit or other object, data exchange with which is carried out by inductive communication in the immediate vicinity of the terminal equipment.VICCVicinity Integrated Circuit Card, a contactless card on a long-range integrated circuit (at distances of about 1 m.) I mentioned this abbreviation here only for general development, I will not write about this protocol.IFDInterface Device, Interface / interface device (named according to GOST R ISO/IEC 7816-7), card reader, reader, terminal equipment.PCDProximity Coupling Device, IFD for PICC, NFC Reader, NFC scanner.ApplicationStructures, data elements, and software modules (based on ICC/PICC) that are required to perform certain functions. Examples of applications: banking (Visa/MasterCard), telephone (SIM for GSM network).APDUapplication protocol data unit: the main data type used for communication between ICC/PICC and IFD/PCD. APDU is a byte array with a specific logical structure.TPDUtransmission protocol data unit, the data block of the transport protocol, it is the TPDU that is used when the IFD (reader) communicates with the ICC (card).
❈ ❈ ❈

The basic minimum unit of data exchange in PC/SC is a byte, that is, an integer of eight bits. Some standards use the term octet instead of the word byte to emphasize the structure of the value, since in theory the byte length can be different on hardware of different architectures. In practice, we always assume that the byte length is eight bits, and we will continue to use this term.

A byte can have a value from 0 to 255 inclusive in decimal representation, but for us it is more convenient to use hexadecimal (hex), and always two-character, that is, a byte can take a value from 0x00up 0xFFto inclusive. Sometimes it is also specified in which form the number is encoded-little endian / big endian. In our domain (as well as in networks in general), the big endian option is adopted by default. That is, in memory (and during transmission), the highest byte comes first and data is transmitted/stored in the same sequence as it is displayed on the screen.

In some situations, it is convenient to separate a byte into components. A byte 0x9Aconsists of two nibbles (nibble in English): 9- the first/highest andA-the second/lowest nibble. The entry 9Xmeans: "any byte in which the highest nibble is equal 9to ".

Bytes are grouped into byte arrays. This is the sequence in which bytes are stored in memory and transmitted over communication channels. In standards and documentation, byte arrays are usually represented as strings of hex literals, separated by spaces for readability:00 90 FA 91 3C, and spaces can sometimes be omitted: 0090FA913C.

The bits that make up a byte are usually numbered from right to left in binary notation. For example, a byte 0xC5in the bit representation looks like this:1 1 0 0 0 1 0 1. And the bits are numbered like this:

b8b7b6b5b4b3b2b1
11000101
In this case, the bit b8is usually called the high bit (higher bit, most significant bit/msb), and b1the low bit (lower, least significant/lsb). Also, an expression like "high three bits "means" bits b8, b7, b6". Informally, it is customary to refer abbreviations in lowercase to bits, and in upper-to bytes, that is, MSB means most significant byte, and msb-most significant bit.

Hierarchical model

A smart card is actually a miniature computer with a microprocessor, memory, and operating system. External systems do not have direct access to its data and must use the provided interface (contact platform or radio interface). Smart cards do not have their own power supply and receive it from the reader via pins (ICC) or an induction coil (PICC).

The PC/SC architecture is hierarchical.

PC/SC Architecture


At the lowest level are the physical readers (card reading equipment) — IFD/PCD and the actual cards-ICC/PICC. An electrical signal protocol is used between cards and readers, it is extremely low-level and operates with electrical characteristics (frequency, voltage, and so on). Each IFD can have its own way of connecting to the computer (USB, COM, PS/2, Firewire, etc.), its own protocol for working through the connection port.

IFD Handlers are located at the level above, in fact, they are something like drivers that convert the specific signal protocol of each IFD into a unified one that is used at the level above.

At the next level — ICC Resource Manager, the resource manager is a key component of the entire infrastructure, it should be the only one in the OS and is usually already provided by the operating system. ICC Resource Manager manages access to all ICCS and IFDs, monitors the appearance of new readers in the system, and enables and disables ICC / PICC. The manager's task is also to separate access before IFD and transaction. This is where all communication between application/system software and readers should take place. No other software should have independent access to the IFD Handler itself. However, the reader may well be available, for example, as a USB device, that is, some other driver can work in parallel with the IFD handler, bypassing the entire PC/SC subsystem.

On Linux, the role of ICC Resource Manager is performed by the pcscd daemon from the pcsc-lite package, which is usually installed separately from the standard repository. In macOS and Windows, the manager is a standard part of the operating system.

All other components (service providers) and operating system programs are located above the ICC Resource Manager hierarchy. They can work either directly through the manager, or through each other. The image above shows several such service providers. For more information, see the introduction to the PC/SC specification (section 2.1.5). One of the most common service providers is a crypto provider that provides a unified interface for application applications to interact with cryptographic applications on smart cards or cryptographic USB tokens.

At the last level are application applications. They can work with both intermediate service providers and directly with the ICC Resource Manager. Naturally, the application must be able to work with the service provider's interface or with the manager's low-level interface.

I just briefly described the main components in the PC/SC architecture, and they are described in detail in the official specification. Later in the text, I will sometimes use the terms and abbreviations just described, for example, instead of "reader" I will write "IFD", instead of" smart card "-" ICC", and instead of" contactless card " — "PICC".

How the IFD and ICC work?

Let's first look at the IFD and ICC state diagram (let me remind you that an ICC is a chip card with a contact pad, this section only covers them, since for contactless cards, the PC/SC mechanism emulates part of the data for uniform representation, but more on that later).

IFD and ICC states


Conceptually, everything is really simple: when the card is connected (power is applied), a RESET signal is sent. This is done via a separate dedicated RST contact area. The chip on the card understands that power has arrived and the initialization process is running, so you need to send back a special byte array ATR (stands for Answer-to-Reset). The card must do this very quickly, otherwise IFD will consider it faulty and stop interacting with it.

I will discuss the structure of the ATR in more detail later in this article. However, I will now note two important points:
  • The ATR is generated by the pin chip card's microprocessor.
  • The ATR contains the necessary information for further work: protocols, manufacturer code, protocol parameters, and so on. The ATR is not a card identifier! Its main goal is to correctly initialize the equipment for further operation.
When the IFD receives an ATR, it parses it, extracts the parameters necessary for a correct connection, and switches to a ready-to-use mode for further interaction. In this mode, communication between the IFD and the ICC occurs by sending and receiving byte arrays called TPDU-Transport Protocol Data Units. And between ICC and application programs, communication takes place via APDUApplication Protocol Data Unit — these are also byte arrays. Sending APDUs and processing responses is the main thing that a PC/SC program does. The IFD (reader) converts the APDU to TPDU, sends the TPDU to the card, receives the response also as TPDU, and converts it to APDU.

Since we are primarily interested in the application layer, we will never deal directly with the TPDU, but will only work with the APDU.

PCD and PICC operation diagram

⭐ Let me remind you that for contactless cards (PICC), the reader (IFD) is called a PCD-Proximity Coupling Device.
There are two types of PICC that differ in signal interface, they are designated as Type A and Type B. Both types are defined in section 7 of ISO/IEC 14443-2. They use the same frequency — 13.56 MHz, but they have different low-level and RF characteristics, as well as different initialization schemes.

The process of initializing a contactless card is much more complicated than for a contact card. Most of it is occupied by the so-called anticollision cycle. A collision occurs when more than one card is simultaneously exposed to the PCD electromagnetic field and the PCD must distinguish these cards from each other. The algorithm for this process is very complex and takes up several dozen pages of description in the ISO/IEC 14443-2 and ISO/IEC 14443-3 standards, so I won't give it here, and we won't really need it — the reader and driver are fully engaged in this.

Also, PICC basically does not have an ATR, instead the PCD receives other data from the card, on the basis of which it establishes a connection. However, for compatibility with ISO / IEC 7816 (which, let me remind you, is the basis of PC/SC), the reader (PCD) itself generates an ATR based on data from the initialization procedure instead of the card. This process is described in section 3.1.3.2.3 of the PC/SC part 3 specification.

In any case, for a PC/SC client, the process of getting started with PICC is practically no different from ICC and logically looks about the same as in the illustration from the previous section. However, it should be borne in mind that a PC/SC reader must understand the specific types of ISO-14443-compliant devices (PICC) and be able to convert application APDUs to the corresponding PICC radio signal commands.

⭐ Different reader manufacturers can convert the same RF PICC commands to different APDU commands. In this article, all the examples are written for ACS ACR122U, and if you try to use a reader from another manufacturer, the written programs may not work.

Data exchange protocols

The physical aspects of the protocol for communicating with a chip card (with a contact pad) are defined in the ISO/IEC 7816-3 standard (GOST R ISO:IEC 7816-3, we are not interested in this, since PC/SC components are responsible for this. Similarly with contactless cards, the physical protocols for which are defined in ISO / IEC 14443.

Before you can start data exchange, the application must specify the transmission protocol that it will use. In PC/SC, these protocols are taken from the ISO/IEC 7816 standard, and are commonly referred to as T=0andT=1:

  • T=0 - symbol-oriented protocol;
  • T=1 — block-oriented protocol.
AndT=0, and T=1are asynchronous and half-duplex, that is, data transfer can only go one way at any time: sent a command → received a response. Character-and block-oriented means what kind/size of minimum data blocks are transmitted, and although these are low-level implementation technical details, the choice of protocol determines which APDUs are allowed to be constructed and how exactly to receive a response to a request. Both protocols have integrity control, T=0which works byte-by-byte and block-by-T=1block. In addition, T=0there may be a situation when the result of executing the command contains too much data, so the status word is returned61 XX, where XXis the number of bytes that need to be read through the special GET RESPONSE command. This continues until all data is transmitted. This point will be discussed in detail in one of the examples below.

Contact Cards (ICC) can support both protocols, or just one. For contactless cards (PICC), only the protocol is allowed in the ISO/IEC 14443-4 T=1standard. In some sources, it is also referred to as T=CL.

Structure of the command-response pair

PS/SC "drivers" take care of all the low-level work with protocols, and clients are left with only the relatively high-level part: sending and receiving bytes in a standardized way.
The command-response pair consists of two consecutive messages: the command APDU (Command APDU, C-APDU, from the application program via the reader to the card) and the immediately following APDU response (Response APDU, R-APDU, from the card via the reader to the application program). Usually, when writing APDU, they mean Command APDU, and I will also follow this rule in the following sections.

Each C-APDU is a sequence of bytes with the following structure (Table from ISO/IEC 7816-4-2020):

Command-Response


The APDU of any command always starts with a fixed-length 4-byte header, which is indicated in the following order:CLA, INS, P1, P2.

CLAclass byte, it defines the command class, if bit b8 is set to0, then it is an inter-industry class, if set to1, then it is a proprietary class, the value 0xFFof the ISO 7816 standard is prohibited for use in real data transmission, but it is used in PC/SC for special commands for the reader. For cross-branch class commands, the byte structure CLAis described in detail in the ISO / IEC 7816-4 standard, but I will not describe it here.Examples. CLA=A0 used for SIM cards, and CLA=8X— for payment/bank cards.INSinstruction byte or command byte, it defines the actual command to be executed. In the industry, it is customary to use the same instruction codes for operations that are similar in meaning. Standard cross-industry commands are given in ISO / IEC 7816-4, and in specific industries they are used in proprietary CLA classes of their own.P1byte of the first parameter, the specific value depends on the command being executedP2byte of the second parameter, the specific value depends on the command being executed
❈ ❈ ❈

The following bytes define the additional data sent with the command and the maximum length of the expected response.

LcThis field Lcencodes the value Nc— the number of bytes in the command data field. It can contain 0, 1, or 3 bytes. The absence of this field means that Ncit is equal to zero, and then the next block is missing. If the field Lcis 1 byte long, it must contain a value Ncbetween 1 and 255 inclusive. If the field length Lcis 3 bytes (extended fieldLc), then the first byte is equal0x00, and the next two define a value Ncfrom 1 to 65535 inclusive. The field Lccannot contain a value 0x00.team dataThe length of this block of bytes must be equal to the value Ncencoded by the previous field.LeThe field Leencodes the value Ne— the maximum number of bytes that we want to receive in the response. If the field Leis missing, Neit is equal to zero, if Le=00, Neit is equal to 256. The extended field Lecan consist of:
  • if the field Lcis missing, three bytes00 XX YY, then bytes XX YYencode the value Nefrom 1 to 65535, bytes 00 00encode the value Ne65536
  • if the field Lcis present, two bytes XX YYthat encode the value Nefrom 1 to 65535, bytes 00 00that encode the value Ne65536
⭐ The and fields LcLecan only be short or extended at the same time.You can't combine different types. The ability to use extended fields LcLeis defined in the ATR value; if this is not explicitly allowed, then only short fields can be used.
❈ ❈ ❈

After sending a C-APDU, the response is returned as an R-APDU. If the command does not return useful data, the R-APDU looks like this:

SW1SW2

If data is returned, then this is:
│ │ │ RESPONSE DATA │ │ │SW1SW2
R-APDU consists of at least two mandatory status bytes SW1 and SW2(SWmeans status word), and if a value Negreater than zero was encoded in the command and the result of the command implies a response, then there will be more bytes before Nrthe status bytes, but no more Ne. If the value Newas set to 256 (for a short field Le) or 65536 (for an extended field Le), all available bytes will be returned.

If there is still data left after the result is returned (this is signaled by the values of the status bytes), then a special method is used to get the remaining data, which I will tell you about in a separate example.

Valid values SW1for the and bytes SW2are also defined in ISO 7816. It is usually accepted to write both of these values in the formXX YY, where XXand YYare the values SW1of andSW2, respectively. For example, the status of the successful completion of the command is—90 00, and other values may indicate errors or the need to execute additional commands.

Possible values SW1for and SW2are usually specified in the documentation and specifications. Here is a page from the PC/SC specification describing the Read Binary command:

Read Binary


❈ ❈ ❈

Such a complex scheme actually boils down to just four different variants of C-APDU.

Option 1:
CLAINSP1P2

Option 2:
CLAINSP1P2Le
The Le field specifies the maximum size of the expected response (from Length expected).

Option 3:
CLAINSP1P2Lc│ │ │ DATA │ │ │
the length of DATA is equal to the Lc value

Option 4:
CLAINSP1P2Lc│ │ │ DATA │ │ │Le
The length of DATA is equal to the value in the Lc field, plus the maximum size of the expected response is specified in the Le field.

INS, CLA, P1etc. are standardized names of the corresponding bytes/fields in the APDU byte array, and can be referenced when describing different APDUs. They appear in all standards that somehow overlap with ISO / IEC 7816.
❈ ❈ ❈

Any interaction with the reader (and, accordingly, with the card) occurs through sending and receiving APDUs. Also, each reader has internal commands that are not transmitted to the card. For example, pseudo-commands or pseudo-APDUs are used to control the LEDs on the case. They also use reserved CLAINScommands for their own operations.

Also, through pseudo-commands, commands are proxied to the card, that is, the reader provides a set of internal commands for managing individual types of cards. For each such special command, it generates card-specific signals. Pseudo-commands are described in the reader instructions.

In the following example, I'll show you how to send commands and receive responses.

example-03: Getting the UID of a contactless card

Source code of the program:
⭐ For this example, you can only use a reader for contactless chip cards. I use contactless cards because they are guaranteed to execute a fixed command with a successful result, and for contact cards this is generally incorrect.
The goals of this example are:

  • show what sending commands and receiving responses look like in the code;
  • show how an APDU is constructed from bytes;
  • show successful execution;
  • show execution with an error.
The UID of a contactless card is a special globally unique value that is assigned to each card by the manufacturer. The UID is defined in the ISO 14443 standard, and each card that meets it must correctly report its UID in response to a standard request.

Here is the entire program code example-03/read-picc-uidfor reading the UID of a contactless card:

#!/usr/bin/env python3

from smartcard.System import readers
from smartcard.CardRequest import CardRequest
from smartcard.util import toHexString

def main() -> int:
reader = readers()[0]
print('Connected reader: {0}'.format(reader))
cardrequest = CardRequest(timeout=None, readers=[reader])
print('Waiting for the card...')
cardservice = cardrequest.waitforcard()
cardservice.connection.connect()

apdu = [0xFF, 0xCA, 0x00, 0x00, 0x00]
response, sw1, sw2 = cardservice.connection.transmit(apdu)

print('Status word: ', toHexString([sw1, sw2]))
print('Response:', toHexString(response))

return 0

if __name__ == '__main__':
main()
Start as in the previous example: connect to the first reader and wait for the card to appear. Next, we form the APDU (let me remind you, this stands for application protocol data unit) as a byte array and send it to the card via the reader:

apdu = [0xFF, 0xCA, 0x00, 0x00, 0x00]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
We get the response and print it out along with the status word:

print('Status word: ', toHexString([sw1, sw2]))
print('Response:', toHexString(response))
Fully running the program together with the result on the screen looks like this:

% python3 example-0/read-picc-uid
Connected reader: ACS ACR122U PICC Interface
Waiting for the card...
Status word: 90 00
Response: 04 91 2A 6A 2F 64 80
Now we will analyze in detail what exactly we caused. The command APDU consists of bytesFF CA 00 00 00:

  • 0xFF — this CLAis the command class; the value 0xFFmeans that the command is intended for the reader, which converts it into the desired sequence of signals when communicating with the card;
  • 0xCA - this is INS, the Get Data instruction code from the PC/SC specification, section 3.2.2.1.3;
  • 0x00 — this is the parameter P1;
  • 0x00 — this is the parameter P2, the value P1=00and P2=00means that we want to get the UID of the card;
  • 0x00 — this is the Le field, the value 00means that we want to get the entire result at once.
So this is Option 2 of C-APDU:

CLAINSP1P2Le
FFCA000000
P1=00The and value P2=00means that we want to get the card's UID.

Passing APDU via the 📚 method of transmit()the class smartcard.CardConnection.CardConnection. The result is an array with the response from the card and status bytes 90 00indicating successful execution of the command.:

Status word: 90 00
Response: 04 91 2A 6A 2F 64 80
⭐ For a class byteCLA, the value 0xFFin the ISO/IEC 7816-4 standard is explicitly marked as invalid, meaning that commands with this class cannot be used for working with ISO-7816 cards (with a contact pad). Therefore, in PC/SC, this value is used for commands intended for the reader, this is described in section 3.2 of the PC/SC specification part 3. Commands in this class are processed by the reader itself and converted to low-level signals for the card, or are not sent to the card at all.
❈ ❈ ❈

In the next programexample-03/read-picc-fail, we will execute the same Get Data command, but P1P2we will pass incorrect values in the arguments and: 0xABand 0xCDaccordingly, it differs from the previous one by one line:

apdu = [0xFF, 0xCA, 0xAB, 0xCD, 0x00]
And the result of its execution is as follows:

% python3 example-03/read-picc-uid
Connected reader: ACS ACR122U PICC Interface
Waiting for the card...
Status word: 63 00
Response:
Returned an empty list of bytes as the response and the value of the status word 63 00. This value is defined in ISO / IEC 7816-4 and means Warning: No information giventhat it is cross-industry, meaning that its semantics are reserved for all applications.

Theory: data on a smart card, card types by data management method.

Smart cards (both contact and contactless) are divided into two groups:

  • microprocessor cards-contain a microprocessor and embedded software for managing data in the card's memory; they are used, for example, in bank cards, electronic passports, and SIM cards;
  • memory cards-contain only data and, possibly, means of restricting access to them. They are used, for example, as electronic travel cards or payment cards in mobile phones. Such cards are significantly cheaper than microprocessor-based ones.
A microprocessor card is a mini-computer with a processor and memory, and this "computer" can store data and run applications. A typical example of an application is a cryptographic one that uses a secret key stored in the permanent memory of a smart card to encrypt / decrypt data from outside the card, and there is no access to the private key from outside the card.

Logically, any microprocessor card consists of the same components, here they are in the diagram along with the connections.

ICC architecture


CPUThe main microprocessor of the card, traditionally used an 8-bit microcontroller with a set of instructions Motorola 6805 or Intel 8051, but now there can be a much more powerful (16 - or even 32-bit) processor.NPUOptional mathematical coprocessor, a microcontroller with a specific task for any narrow task, for example, for cryptographic operations.I/O subsystemAn input/output subsystem that passes all data from and to the card.RAMRandom access memory — RAM that is cleared after a power outage. Usually, the RAM size is very small.ROMRead only memory-permanent memory, the data written on it cannot be changed, for example, the operating system, cryptographic and other basic programs are written to ROM.EEPROMElectronically erasable programmable read only memory — rewritable memory whose contents do not disappear after a power outage.
❈ ❈ ❈

The memory card (storage card) is much simpler, there is no full-fledged microprocessor inside, but only a simple inexpensive chip that can perform fixed simple operations. It works something like this:

ICC architecture


I/O subsystemAn input/output subsystem that passes all data from and to the card.ROMRead only memory — permanent memory, the data written on it cannot be changed. Memory cards store in ROM, for example, a unique identifier of the card that is assigned when it is made.EEPROMElectronically erasable programmable read only memory — rewritable memory whose contents do not disappear after a power outage. The size of this memory for this type of card is usually very small, measured literally in kilobytes.Addressing and access control logicThis is a simple chip, usually proprietary with a closed architecture, its role is to process signals from the I / O subsystem and write/read memory blocks to the EEPROM. This chip is also responsible for delimiting access to certain memory blocks.
❈ ❈ ❈

The most common contactless memory cards are cards with chips of the Mifare family (this trademark belongs to NXP Semiconductors, one of the largest chip manufacturers). They are issued both in the form of traditional plastic cards, and in the form of key chains, stickers, rings, etc. The most famous example of their use is a variety of transport cards: Moscow "Troika", St. Petersburg" Podorozhnik", transport card of the company" Zolotaya Korona " and so on.

There are also contact memory cards, for example, SLE 4418, and there will be a separate section about working with them. But first, let's dive into the details of working with Mifare cards, there will be a lot of theory and a few examples that demonstrate different scenarios for using different chips. I specifically pay a lot of attention to working with contactless cards, since today this is the largest segment that is used literally everywhere.

example-04: determining the type of contactless memory card

The purpose of this example is to show how to extract information about the card name from the ATR.

There is no simple way to determine the chip type and card manufacturer.To do this, you need to analyze the data received from the initialization procedure at a low level. However, the PC/SC specification describes how the ATR value for contactless cards should be generated based on initialization data (PC / SC Part 3 section 3.1.3.2.3), so we can relatively easily determine the protocol type and chip of the contactless card.

For a contactless memory card, the ATR contains information about the card protocol and its name. It is stored in historical bytes. A detailed analysis of the ATR structure will be discussed later in this article. For now, we will simply use the data from the specification.

The ATR for contactless memory cards has the following structure::

3B 8n 80 01 80 4F LL A0 00 00 03 06 SS NN NN 00 00 00 00 TT
  • 3B — ATR header, fixed value
  • 8n - the lowest nibble contains the length nof a block of backstory bytes that starts with 4 ATR bytes
  • 80 — fixed value
  • 01 — a fixed value, immediately followed by a block of prehistory bytes of length n
    • 80 — first byte of a block of prehistory bytes
    • 4F — fixed value
    • LL — length of the next byte array
      • A0 00 00 03 06 — fixed value
      • SS - byte that encodes the card standard
      • NN NN — two bytes that encode the card name
      • 00 00 00 00 — four bytes are reserved for future reference and must contain zeros
  • TT — ATR checksum
The known PC/SC values SSNN NNare given in a separate specification document, so our code essentially boils down to getting these values and searching through pre-compiled tables.

Unfortunately, this list is updated only at the request of card manufacturers, so there is no guarantee that your existing card will be included, even if it is compatible with one of the standards. Therefore, the algorithm given in this example will only work on the cards that are in the list.
Search first, and we immediately discard cards that we don't support:

# extract length of Historical bytes field
hb_len = atr[1] & 0xF

# extract Historical bytes
hb = atr[4:hb_len+4]

if hb[:2] != [0x80, 0x4F]:
print('Unsupported card type')
return 1

if hb[3:8] != [0xA0, 0x00, 0x00, 0x03, 0x06]:
print('Unsupported card type')
return 1

standard_byte = hb[8]
cardname_bytes = hb[9:11]
Here we store the values SSof and NN NNin the variables standard_byteandcardname_bytes, respectively. To convert these values to a readable format, we will search for them in pre-compiled dictionaries that are stored in variables KNOWN_STANDARDSand KNOWN_CARD_NAMES. They are quite large and therefore I do not include them here.

if standard_byte not in KNOWN_STANDARDS:
print('Unknown standard')
return 1
print('Card standard:', KNOWN_STANDARDS[standard_byte])

cardname_word = 256*cardname_bytes[0] + cardname_bytes[1]
if cardname_word not in KNOWN_CARD_NAMES:
print('Unknown card name:', toHexString(cardname_bytes))
return 1
print('Card name:', KNOWN_CARD_NAMES[cardname_word])
And then there are some examples of how the program works on various contactless cards.

❈ ❈ ❈

The old Troika card is a classic Mifare Classic 1K card:

% python3 example-04/detect-picc-type
Connected reader: ACS ACR122U PICC Interface
Waiting for card...
Card connected
Card standard: ISO 14443 A, part 3
Card name: Mifare Standard 1K
❈ ❈ ❈

New card "Plantain", this is a newer Mifare Classic 4K card:

% python3 example-04/detect-picc-type
Connected reader: ACS ACR122U PICC Interface
Waiting for card...
Card connected
Card standard: ISO 14443 A, part 3
Card name: Mifare Standard 4K
❈ ❈ ❈

Paper ticket "United" of the Moscow metro, this is the cheapest and weakest card of the Mifare — Ultralight family:

% python3 example-04/detect-picc-type
Connected reader: ACS ACR122U PICC Interface
Waiting for card...
Card connected
Card standard: ISO 14443 A, part 3
Card name: Mifare Ultra light
❈ ❈ ❈

Mastercard bank card with contactless chip (this is no longer a memory card, but a full-fledged microprocessor smart card):

% python3 example-04/detect-picc-type
Connected reader: ACS ACR122U PICC Interface
Waiting for card...
Card connected
Unsupported card type
❈ ❈ ❈

Important points from this example
  • Memory cards and microprocessor cards are two completely different classes of devices.
  • PC/SC makes it relatively easy to determine the protocol and name of the memory card.
  • Contactless cards do not have an ATR, but PC/SC generates it to unify work with all types of cards: both contact and contactless.

example-05: An application for interactive work with APDU queries.



For this example, you can use both types of readers: contactless and contact chip cards.
The goals of this example are:

  • write a simple console application for interactive work with the card in the reader:
    • sending the entered C-APDUs;
    • checking the input data;
    • displaying the response R-APDU;
    • error handling;
    • storing the command history in a file and restoring it on the next run;
  • continue using this app for interactive experiments with the card.
This type of application is called a REPL (Read-Eval-Print Loop) and allows you to interactively work with some other application or hardware. Python already has built-in REPL implementation tools and you can easily implement the usual functionality: a history of previous commands, search for it, save history from past runs, built-in help, and so on.

Our program will work very simply: at startup, we wait for the card in the reader, and when it appears, we start an infinite loop of input and execution of commands. If there is no card in the reader when executing the command, or an error is returned from the reader, then we show a message about this, but we do not terminate the program.

We will interpret the following commands as correct:

FFA400000106FF A4 00 00 01 06FF A4 0000 0106All this will be interpreted as a standard APDU command. After entering the APDU, it will be displayed in the normalized form:FF A4 00 00 01 06, that is, a set of octets separated by spaces.exitquitThese commands will be used to terminate the program. You can also use the standard Ctrl+D keyboard shortcut for terminal programs to exit.FFA400000106 # select SLE cardCommand comments and all characters from the beginning #to the end of the line will be ignored. Also, extra spaces inside commands and spaces at the beginning and end of the command are ignored.We also completely ignore empty commands.
A line with a prompt for input will look like APDU%this: commands sent to the reader are shown with a character >at the beginning of the line, and commands received from the reader are shown with a character <. The program outputs errors with a prefix <<<at the beginning of the line.

From the standard modules, we will need:

import atexit
import readline
import os
import re
At startup, we initialize our terminal / shell:

# repl initialization
history_file = os.path.expanduser('~/.apdu-terminal.history')
def terminate():
print('Exiting...')
readline.write_history_file(history_file)
atexit.register(terminate)
if os.path.exists(history_file):
readline.read_history_file(history_file)
readline.set_history_length(1000)
We store the command history in a file~/.apdu-terminal.history. It is automatically read at startup (readline.read_history_file) and saved at exit (the function is automatically called terminateat program termination, this is done via a callatexit.register(terminate)). Use the up/down arrow keys to navigate through the history.

Next, the standard initialization of the reader and the output of a small help:

reader = readers()[0]
print('Connected reader: {0}'.format(reader))
cardrequest = CardRequest(timeout=None, readers=[reader])
print('Waiting for card ...')
cardservice = cardrequest.waitforcard()
cardservice.connection.connect()
print('Card connected, starting REPL shell.')
print('type "exit" or "quit" to exit program, or press Ctrl+D.')
I'll comment on the main REPL loop directly in the code listing:

while True:
try:
# here we read the command entered by the user and specify the prompt string
cmd = input('APDU% ')
# after receiving the string, we perform its basic normalization
# delete all text from the '#' character to the end of the line
cmd = re.sub('#.+', '', cmd)
# remove spaces at the beginning and end of the line
cmd = cmd.strip()
except EOFError:
# here we process the Ctrl+D key combination, which we interpret as exiting the program
# our exit is simply interrupting the endless loop while True
break
if cmd in ('exit', 'quit'):
# process 'exit' and 'quit' commands
break
# here we indicate the list of ignored commands, for now there is only an empty line
if cmd in ('',):
continue
try:
# trying to convert the entered command into a byte array,
# errors are caught in the TypeError exception handler below
apdu = toBytes(cmd)
# print the normalized APDU request
print('>', toHexString(apdu))
# send APDU to the reader
response, sw1, sw2 = cardservice.connection.transmit(apdu)
# We format the output differently, depending on the length of the byte array with the response
if len(response) == 0:
print('< [empty response]', 'Status:', toHexString([sw1, sw2]))
else:
print('<', toHexString(response), 'Status:', toHexString([sw1, sw2]))
except TypeError:
print('<<< Invalid command.')
except CardConnectionException as e:
print('<<< Reader communication error:', str(e))
❈ ❈ ❈

Here's what the app session looks like:
% ./apdu-terminal
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected, starting REPL shell.
Type "exit" or "quit" to exit program, or press Ctrl+D.
APDU% FFA400000106 # select SLE card
> FF A4 00 00 01 06
< [empty response] Status: 90 00
APDU% FF B0 00 00 20 # read some bytes from SLE card
> FF B0 00 00 20
< A2 13 10 91 FF FF 81 15 FF FF FF FF FF FF FF FF FF FF FF FF FF D2 76 00 00 04 00 FF FF FF FF FF Status: 90 00
APDU% exit
Exiting...
❈ ❈ ❈

What else you can do in this app (ideas for development):
  • add built-in help for the commandhelp;
  • add the ability to select a specific reader if there are several of them connected;
  • add more error handlers (for example, now the program crashes if no reader is connected to the system at startup);
  • add handling of a situation when the reader is disconnected and reconnected during operation;
  • add automatic interpretation of known response types and print them out after displaying a " raw " list of bytes;
  • output formatting, such as outputting bytes in strings of 16 or 32;
  • add the ability to specify additional parameters when entering APDU (for example, specify exactly how to interpret the response bytes and in what format to display them).

Theory: Mifare Classic 1K/4K memory cards.

Mifare has its own signal protocol (between the card and the reader) and its own scheme for working with data. These cards do not follow the ISO/IEC 7816 standard, so they do not understand the standard commands described there. However, PC/SC defines instructions in a class FFthat many readers understand and correctly translate into low-level commands for a specific card type, in our case Mifare. The full specification of the card protocol can be found in the official document of the manufacturer of these NXP cards: MIFARE Classic EV1 1K datasheet, MIFARE Classic EV1 4K datasheet.

NXP also licenses its technology to other companies that produce their Mifare Classic-compatible cards. For example, these are Infineon's SLE 66R35, Fudan Microelectronics ' FM1108, and others.

❈ ❈ ❈

On the Mifare Classic 1K card, one kilobyte (1024 bytes) is stored in rewritable EEPROM memory, this memory is divided into 16 sectors, access to each sector is controlled by two different keys, which are designated as Key A and Key B. Each sector is divided into 4 blocks of 16 bytes.

The "addresses" of sectors start from zero: the first one has an address 0x00, the second one — 0x01and so on up to the sixteenth sector with an address 0x0F.

Blocks have end-to — end addressing across sectors and also start from zero .For example, a sector 0x00consists of blocks with addresses{0x00, 0x01, 0x02, 0x03}, a sector 0x01consists of blocks {0x04, 0x05, 0x06, 0x07}, and a sector 0x0Fconsists of blocks{0x3C, 0x3D, 0x3E, 0x3F}.

❈ ❈ ❈

The Mifare Classic 4K card has four kilobytes of EEPROM memory, divided into 32 sectors of 4 blocks plus 8 sectors of 16 blocks, and the block size is also 16 bytes, just like the Mifare Classic 1K.

The "addresses" of sectors start from zero: the first one has an address 0x00, the second one — 0x01and so on up to 40 sectors with an address 0x27.

Blocks have end-to — end addressing across sectors and also start from zero .For example, a sector 0x00consists of blocks with addresses{0x00, 0x01, 0x02, 0x03}, a sector 0x1Fconsists of blocks {0x7C, 0x7D, 0x7E, 0x7F}, a sector 0x20(the first sector of 16 blocks) consists of {0x80, 0x81, ..., 0x8F}blocks , and , finally, the last sector 0x27consists of blocks{0xF0, ... 0xFE, 0xFF}.

The Mifare Classic 4K card can be used wherever Mifare Classic 1K is used — the scheme of working with the first 16 sectors is the same.

❈ ❈ ❈

The first memory block of the card (with the address 0x00) is reserved for the manufacturer's data and contains, in particular, the UID. Theoretically, it is protected from overwriting, but there are special cards where you can change the data in this block.

External clients do not have direct access to these blocks and cannot write arbitrary information there. Also, you can't use all 1024/4096 bytes to write data. Part of this space is reserved for storing keys and access conditions. You can only read or write the entire block (i.e., 16 bytes), if the access conditions allow it. You can't write/read individual bytes or arbitrary sections of memory.

The last block of each sector is called a sector trailer and contains (in this order from the zero byte): Key A (6 bytes, required), access conditions (access conditions, access bits, access control bits, 3 bytes), user byte (you can write any value there), Key B (6 bytes). Instead of the B key, if you don't need it, you can store any other data.

To read or write to the card, you must know the key. The card does not provide unprotected/anonymous access. Before any operation with the card's memory, mandatory authentication is required. It is performed separately for each of the blocks whose data needs access. For authentication, the previously mentioned Key A (or Key B) is used. For all operations, external clients use special instructions, which are then translated by the reader into the card protocol. A read / write operation is performed in three instructions:

  1. write 6 bytes of the key to the reader's memory;
  2. run the authentication command for the required block;
  3. perform the necessary operation with this block.
The PCD (Proxymity Coupling Device) translates the C-APDU to the card's signal protocol, and then translates the response back to the R-APDU.

Valid operations with sector memory are determined by the access conditions from the sector trailer. For each block, you can set access conditions (for Read, Write, Increment, Decrement, Restore operations) and which key to use for authentication (Key A or Key B). Access conditions for the trailer block are set separately (read, write).

⭐ A typical usage scenario, for example, is as follows: Key A is used for initialization/writing and is kept secret, while Key B is read-only and comes with the software for using the card.
Depending on the parameters in the access conditions, each block can be either a binary block (read/write block, i.e. a set of bytes that can be read and written), or a numeric block (value block, i.e. an integer of 4 bytes is stored in the block). In addition to the Read and Write operations, Increment, Decrement, Transfer, and Restore operations can also be applied to a numeric value in a block. A numeric value is a four-byte signed integer, only one value can be stored in a block, and it is repeated three times within the block bytes to check integrity. The reader usually encapsulates all operations with a numeric block, that is, for writing, for example, you just pass a simple number in the desired command, and its encoding in 16 bytes of the block is performed by the reader.

Working with Mifare Classic 1K cards goes beyond the boundaries of the PC/SC specification (and this article), each reader manufacturer implements its own commands for this. However, they try to use standard instruction codes for these operations, so it is highly likely that the instructions will be approximately the same for different readers.
❈ ❈ ❈

The access conditions for each block are three bits. They are packed in a special way in three bytes from the trailer. The specification describes the storage method in detail, as well as tables explaining exactly how to interpret different combinations of these bits for all types of blocks. I'll tell you more about access bits in one of the examples below.

⭐ You can only write data to the byte block for Key A, but you can't read it. These bytes are always returned filled with zeros when reading. The byte block for Key B can be made readable by a specific combination of access bits, but in this case the data stored there cannot be used to access the data as a key.
Theoretically, this approach to data management can achieve very impressive results. You can use different sets of keys A and B to save the necessary information to the appropriate blocks and safely update it. However, this card format (or rather, some card revisions) has been hacked for a long time and is therefore completely insecure: all information (and keys, too) can be easily extracted, and if desired, you can make a complete copy of the card using special Chinese blank cards.

On the other hand, there are ways to detect spoof cards, which are also being improved.

The life cycle of a Mifare Classic card is usually as follows:
  1. The factory produces a chip with standard keys and access conditions.
  2. A batch of chips is delivered to the carrier manufacturer.
  3. The manufacturer inserts chips into various shells: a card, keychain, sticker, etc. It may also draw something on the card.
  4. The card is delivered in bulk to a service provider, such as the metro.
  5. The provider personifies the card, i.e. fills it with the required values, registers the ID in its database, sets new access conditions, and changes keys.
  6. The card goes to the end user.
When I write "card", I mean a chip, physically the carrier of this chip can be a sticker, keychain, etc.
In addition to Mifare Classic, there are more secure microprocessor cards, but they have their own protocol, arranged in a completely different way. There are also more secure Mifare cards, such as Mifare Plus, which have a backward compatibility mode with Mifare Classic, but they are just as vulnerable in this mode. There are also cards with more robust algorithms, such as AES.
And now some practical examples of working with the Mifare Classic card.

example-06: Reading the Mifare Classic 1K / 4K card

⭐ For this example, you can only use a reader for contactless chip cards.
Source code of the program: example-06/read-mifare-classic

The goals of this example are:
  • preparing the reader to read data (authentication) according to the PC/SC specification;
  • reading data from the first sector of the Mifare Classic 1K/4K card;
  • error handling.
You will need a Mifare Classic 1K or Mifare Classic 4K card that you know the first sector access key for. A clean, factory-issued, non-personalized card is ideal. You can also try a contactless transport pass ("Troika", "Plantain", "Unified Transport Card"), they are often made on the basis of Mifare Classic 1K chips. Even if you don't know the access keys, you can still try to follow the example for your existing cards.An electronic pass, an intercom keychain, or something else may be suitable.

This time, there won't be a full program listing either, just code snippets with explanations, and a link to the full program code at the beginning of this section.

Let me remind you that in Mifare Classic 1K/4K data is represented in blocks, we read the entire block at once. To read it, we need to know the key to this sector. Since we don't know the key, let's try a few standard options that are used by different manufacturers on "clean" cards.

First, we will set several key variants, each of them 6 bytes long:

keys = [
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
[0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0],
[0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1],
[0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
]
For each of the keys, we need to perform two operations: load the key into memory and try to authenticate with this key loaded into memory.

First, we load the key (it is stored in the key variable):

# 1. load key
# CLA INS P1 P2 Lc Data (6 bytes)
apdu = [0xFF, 0x82, 0x00, 0x00, 0x06] + key
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if (sw1,sw2) != (0x90,0x00):
print('Mifare Load Key failed for key {}'.format(toHexString(key)))
continue
The command's APDU looks like this:FF 82 00 00 06 XX XX XX XX XX XX, where XX XX XX XX XX XXare the key bytes from the key variable.

  • CLA = FF, let me remind you that the value FFis not allowed in ISO 7816 and commands for the reader are passed in this class
  • INS = 82, code of the Load Keys instruction, which is defined in the PC/SC specification Part 3, section 3.2.2.1.4
  • P1 = 00, a set of bits that defines the key structure; in this case00, it means that the key is stored in volatile memory, that is, the reader will forget it after a power outage. ACR122U does not support other modes. Other reader models may store the key in permanent memory.
  • P2 = 00, the key number, for ACR122U can be either 00, or 01. In fact, this is the conditional number of the reader's memory cell to write the key to, and we will pass the same value in the next command.
  • Lc = 06, the length of the data block for the command in which we pass a key with a length of 6 bytes
  • actual key data, 6 bytes
Error codes for Load Keys are defined in the PC/SC specification, and you must process them all if you are writing a normal program. However, it is not guaranteed that the reader will return a correct error.It often returns a status word 63 00without specifying what exactly went wrong.

❈ ❈ ❈

The next stage is authentication, which is performed for a specific block. The standard General Authenticate instruction is used here. It is described in the PC/SC Part 3 specification, section 3.2.2.1.6.

# 2. authentication
# CLA INS P1 P2 Lc Data
apdu = [0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x60, 0x00]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if (sw1,sw2) != (0x90,0x00):
print('Mifare Authentication failed with key {}, status word {:02x} {:02x}'.format(toHexString(key), sw1, sw2))
sleep(0.1)
else:
print('Authenticated with key "{}"'.format(toHexString(key)))
authenticated = True
break
The command's APDU looks like this: FF 86 00 00 05 01 00 00 60 00

  • CLA = FF
  • INS = 86, the General Authenticate command
  • P1 = 00, must stand for zero
  • P2 = 00, must stand for zero
  • Lc = 05, data length for the command, 5 bytes
  • actually data, they have the following structure
    • 01 — version, currently only allowed 01
    • 00 — the highest byte of the block number
    • 00 — the lowest byte of the block number, in our case we want to read the very first block, it is with the number 00
    • 60 — the type of key we want to use for authentication 60means Key A, 61— Key B.
    • 00 — number of the cell where the key was written in the previous Load Keys call
At this stage, the reader can return a special error in the status word if you specified an incorrect key at the previous stage. However, this is also not guaranteed and you may get a generic code again 63 00.

⭐ A few words about block addressing. The scheme with high and low bytes allows you to address 65536 blocks with addresses from 0x0000to 0xFFFF. In Mifare Classic 1K, there are only 64 blocks, so the high byte will always be zero, and the low byte can be in the range from 0x00to 0x3F. Mifare Classic 4K has 256 blocks, so similarly, the high byte will be zero, and the low byte will be in the range from 0x00to 0xFF.
⭐ Pay attention to the code sleep(0.1), we added a delay of one-tenth of a second, as some Mifare cards do not allow you to authenticate with a different key too quickly, so we wait before each next attempt. Sometimes this waiting time should be extended to several seconds.
If we have selected the right key and successfully authenticated, we can continue reading data from the first block.

❈ ❈ ❈

We want to read the block that we authenticated for at the last stage. The instruction is Read Binary and is described in the PC/SC Part 3 specification, section 3.2.2.1.8.

# 3. read data
# CLA INS P1 P2 Le
apdu = [0xFF, 0xB0, 0x00, 0x00, 0x10]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if (sw1,sw2) != (0x90,0x00):
print('Mifare read data failed, status word {:02X} {:02X}'.format(sw1, sw2))
return 1
The command's APDU looks like this: FF B0 00 00 10

  • CLA = FF
  • INS = B0, the Read Binary command
  • P1 = 00, the highest byte of the block number
  • P2 = 00, the lowest byte of the block number
  • Le = 10, size of the expected result, 16 bytes (10 in hexadecimal representation)
If we correctly guessed the key and it can be used to read data, then we will get 16 bytes of the content of the first block. Example of how the program works on a hotel pass card:

% python3 ./example-06/read-mifare-classic
Connected reader: ACS ACR122U PICC Interface
Waiting for Mifare Classic 1K card...
Card connected
Mifare Authentication failed with key A0 B0 C0 D0 E0 F0, status word 63 00
Mifare Authentication failed with key A1 B1 C1 D1 E1 F1, status word 63 00
Mifare Authentication failed with key A0 A1 A2 A3 A4 A5, status word 63 00
Authenticated with key "FF FF FF FF FF FF"
Got response: 13 85 17 6C ED 08 04 00 01 25 AC 59 3B B3 45 1D
On the Mifare card, the first block stores service information. It is usually protected from overwriting. In particular, the UID is recorded there.

You can experiment with the code. For example, to return the contents of the second block (with the address0x0001), send a command in the third stage with the following APDU: FF B0 00 01 10. Note that after successful authentication, the Read Binary statement will use the same key for all attempts to access any of the four blocks in the same sector. So you can authenticate for a block 0x0000and try to read data from the other three blocks without having to authenticate again. However, you will no longer be able to read blocks from another sector.

Conventional cards (such as transport cards) use only a few sectors for data storage, and the standard access key is often left for the first sector. For example , in the Troika card, the key A of the first sector is:A0 A1 A2 A3 A4 A5, and in the Plantain card — FF FF FF FF FF FF.

❈ ❈ ❈

Important points from this example
  • In the fourth block of each sector, keys are stored (only for this sector!) and access conditions for all four blocks of the sector.
  • In total, two keys can be stored in a sector: Key A and Key B.
  • You can set your own access parameters for each block.
  • Any operations with data on the Mifare Classic card require pre-authentication for a specific block, after which you can access the remaining blocks of the sector without authentication (with the same key).
  • Authentication is performed in two stages: first, the key is loaded into the reader, and then authentication is performed with a reference to the previously loaded key.
  • The key lives in the reader until it is disabled, if it was loaded into volatile memory.
  • If you load the key into non-volatile memory, it will still be available after disabling it.
  • Only two keys can be loaded into ACR122U simultaneously and only in volatile memory.
  • Each reader has its own characteristics, so be sure to read the documentation.
  • For the new card, keys A and B are set to default values, which are different for different manufacturers.

example-07: Parsing data on a Mifare Classic 1K / 4K card.

⭐ For this example, you can only use a reader for contactless chip cards.
Source code of the program: example-07/dump-mifare-classic

The goals of this example are:
  • try to read all blocks by iterating over a fixed set of keys;
  • analyze the access conditions and display them on the screen;
  • display the contents of blocks that were read.
In fact, this example is a slightly more complicated version of the previous one. The scheme of the program is approximately as follows:

  • detect the card type and display an error if it is not Mifare Classic 1K/4K;
  • set a list of key constants;
  • go through all sectors of the card and try to authenticate on each one with all predefined keys;
  • try to read the contents of all blocks when authentication is successful;
  • try to read the access conditions for each sector;
  • beautifully display the result on the screen in a pseudo graphic:
    • show for each of the sectors which key is detected (A, B, A and B, none);
    • show data for each block, if it was possible to read it;
    • show access bits for each block.
In example 4, we defined the card type, but in this case we will not do this, but immediately assume that the card in the reader is Mifare 1K/4K. We will try to read only the first 16 sectors.

I will not give the entire text of the program, since it mostly consists of code for a beautiful printout of the read data. But a few features are worth noting.

Here is a function for parsing access bytes into an array of access bits:

def unpack_access_conditions_bits(ac_bytes):
bit = lambda pos, b: ((b >> pos) & 1)
#ac0 = ac_bytes[0] # we don't need that byte
ac1 = ac_bytes[1]
ac2 = ac_bytes[2]
return [
[bit(4, ac1), bit(0, ac2), bit(4, ac2)],
[bit(5, ac1), bit(1, ac2), bit(5, ac2)],
[bit(6, ac1), bit(2, ac2), bit(6, ac2)],
[bit(7, ac1), bit(3, ac2), bit(7, ac2)]
]
At the input, we transmit three bytes from the sector trailer, in which the access conditions for the sector blocks are encoded; at the output, we get an array of access bits (three bits) for each of the blocks.

Next, functions that use the access bits to determine what can be done with the block:

def can_read_block_with_key_a(ac_bits):
return ac_bits in [ [0,0,0], [0,1,0], [1,0,0], [1,1,0], [0,0,1] ]


def can_read_key_b_bytes(ac_bits):
return ac_bits in [ [0,0,0], [0,0,1], [0,1,0] ]
❈ ❈ ❈

The result of the program looks something like this (the Golden Crown travel card is used):

% ./dump-mifare-classic
Connected reader: ACS ACR122U PICC Interface
Waiting for Mifare Classic 1K/4K card...
Card connected, reading data, please wait...
Reading sectors: ................
Sector 00
block 00: 41 A7 13 48 FF 88 04 00 43 25 CC 15 00 18 0C 05, Key A: A0 A1 A2 A3 A4 A5, Key B: ?? ?? ?? ?? ?? ??, AC: 1 0 0
block 01: 0C 0F 18 FF 00 00 00 00 18 EE 00 00 18 EE 00 00, Key A: A0 A1 A2 A3 A4 A5, Key B: ?? ?? ?? ?? ?? ??, AC: 1 0 0
block 02: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05, Key A: A0 A1 A2 A3 A4 A5, Key B: ?? ?? ?? ?? ?? ??, AC: 1 0 0
block 03: 00 00 00 00 00 00 78 77 88 83 00 00 00 00 00 00, Key A: A0 A1 A2 A3 A4 A5, Key B: ?? ?? ?? ?? ?? ??, AC: 0 1 1
Sector 01
block 04: ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??, Key A: ?? ?? ?? ?? ?? ??, Key B: ?? ?? ?? ?? ?? ??, AC: ? ? ?
block 05: ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??, Key A: ?? ?? ?? ?? ?? ??, Key B: ?? ?? ?? ?? ?? ??, AC: ? ? ?
block 06: ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??, Key A: ?? ?? ?? ?? ?? ??, Key B: ?? ?? ?? ?? ?? ??, AC: ? ? ?
block 07: ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??, Key A: ?? ?? ?? ?? ?? ??, Key B: ?? ?? ?? ?? ?? ??, AC: ? ? ?
Sector 02
block 08: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF, Key A: FF FF FF FF FF FF, Key B: FF FF FF FF FF FF, AC: 0 0 0
block 09: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF, Key A: FF FF FF FF FF FF, Key B: FF FF FF FF FF FF, AC: 0 0 0
block 0A: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF, Key A: FF FF FF FF FF FF, Key B: FF FF FF FF FF FF, AC: 0 0 0
block 0B: 00 00 00 00 00 00 FF 07 80 D2 FF FF FF FF FF FF, Key A: FF FF FF FF FF FF, Key B: FF FF FF FF FF FF, AC: 0 0 1
Sector 03
...
and other sectors

The diagram below shows how one sector is arranged, its trailer, and how access bits for the fourth block of the sector are packed:

mifare-block-structure-e.svg


In fact, each bit is duplicated in inverted form, and thanks to this redundancy, all 12 access bits are packed into 24 bits in three bytes. In the position marked as Bytes of the key A, you can write the key A, but when reading here, there will always be zeros. In the position marked as Bytes of the key B, you can write the Key B (in this case, these bytes will contain zeros when reading), or any six bytes, the mode is determined by the access bits.

The combination of bits C1, C2, and C3 for the first three blocks of a sector defines the following access conditions::

C1C2C3What you can do (you can't do anything else)
000All operations for keys A or B are allowed
010Read(А,B)
100Read(A,B), Write(B)
110Read(A,B), Write(B), Increment(B), Decrement/Transfer/Restore(A,B)
001Read(A,B), Decrement/Transfer/Restore(A,B)
011Read(B), Write(B)
101Read(B)
111All operations for both keys are prohibited
Here, a write Read(A,B)means that the block is allowed to be read by the key A or B, Write(B)— only the block is allowed to be written by the key B, and so on. Let me remind you that a block in mifare classic cards is written only in its entirety.

As can be seen from this table, the B key has the maximum capabilities: it is almost always possible to read data with the B Key, and the A key is mainly used for reading.

The combination of bits C1, C2, and C3 for the sector trailer defines the following access conditions to the trailer block:

C1C2C3What you can do (you can't do anything else)
000WriteA(A), AccessRead(A), ReadB(A), WriteB(A)
010AccessRead(A), ReadB(A)
100WriteA(B), AccessRead(A,B), WriteB(B)
110AccessRead(A,B)
001WriteA(A), AccessRead(A), AccessWrite(A), ReadB(A), WriteB(A)
011WriteA(B), AccessRead(A,B), AccessWrite(B), WriteB(B)
101AccessRead(A,B), AccessWrite(B)
111AccessRead(A,B)
Here WriteA(B)it means that you can overwrite key A with key B, ReadB(A)it means that you can read key B with key A, AccessRead(A)you can read access control bytes with key A, and so on.

Please note that you can only use the B Key for access control if the trailer access conditions do not allow the B Key to be read. In other words, if you allow bytes to be read to store Key B, you will not be able to use these bytes to read / write other blocks of the sector, even if access is allowed there with Key B. The A key can never be read. Access bits can always be read by either key A or key B, if this key is allowed to be used. If one of the elements is forbidden to read, then zeros are returned in it.

❈ ❈ ❈

Using these tables, you can now read the last column in the program's results. For example, the bit set 1 0 0for the first three blocks means Read(A,B),Write(B), that is, the data in the block can be read with the key A or B, but can only be written with the key B. And the set of bits 0 1 1in the trailer block meansWriteA(B),AccessRead(A,B), AccessWrite(B), WriteB(B), that is, you can use the B key to write the A or B key, you can read access bits with the A or B key, but you can only write access bits with the B key.

Tables with the access bit structure are created based on data from the Mifare Classic card specification, see sections 8.7.2 Access conditions for the sector trailer and 8.7.3 Access conditions for data blocks.

❈ ❈ ❈

There is also a combination of access bits, which in the specification is called transport (transport), for data blocks this0 0 0, for a trailer — 0 0 1. This is considered the initial configuration for the new card. It is delivered from the manufacturer in this form. In this configuration, the A key can read and write all blocks, read and write access bits and bytes of the B key, and write bytes of the A key.

Important points from this example
  • In fact, the user has access to 768 bytes out of 1024, the rest is allocated for service information.
  • Selecting keys according to this scheme is a very inefficient operation.
  • If you set the access bits incorrectly, you can completely cut off access to the block.
  • The new "factory" card uses standard keys that depend on the manufacturer.
  • Some cards are protected against key iteration. For them, the General Auth operation will return an error for a certain time interval if the previous attempt was unsuccessful. To get around this, you need to insert an artificial delay of a couple of seconds into the code before each General Auth operation.

example-08: Practical example: using a Mifare Classic 1K/4K card as a payment card

⭐ For this example, you can only use a reader for contactless chip cards.
I also recommend using a new clean Mifare Classic 1K card without data for experimentation.

The goals of this example are:
  • planning the data placement structure;
  • demonstration of recording data to a Mifare Classic 1K memory card:
  • recording keys;
  • recording data;
  • record access conditions.
At the very beginning, I talked briefly about the life cycle of a smart card. Personalization is the preparation of the card before it is issued to the customer. In this example, we will write code for personalization, as well as for "real" use of the card. As a basic model, let's take an electronic travel card: the card shows how much money is stored there. A contactless card reader will perform different roles depending on the running program: initializing the card, adding funds to the card, or debiting money.

So, the requirements for our travel card:
  • the card stores the balance as a positive integer;
  • the reader in the role of "issuer" can "issue" the card:
    • access keys are written to a specific sector;
    • access rights to the trailer and the first block of the sector are set for the sector;
    • a zero balance is recorded in the first block;
  • the reader in the "cashier" role can perform the following operations::
    • read and display the current card balance;
    • increase your balance by a certain amount;
  • the reader in the role of "conductor" can write off a fixed amount, that is, reduce the recorded balance by a certain number;
  • the reader in the "issuer" role can "destroy" the card, that is, return the entire sector to its original state, delete the recorded data, and set the initial keys and access conditions.
We will use a separate application for each of the roles. All applications will use a common configuration file that contains the following parameters::

  • sector and block number for storing the balance amount;
  • source key for this sector for a clean card;
  • keys for this sector that will be used for working with the balance;
  • the" cost " of the trip, i.e. how much money will be withdrawn from the card when it is used for payment.
In addition to the actual programs in the directory of this example is the file config. ini, which contains all the parameters of our system:

  • number of the sector and block in it that we use to store data;
  • the source key is A for this sector;
  • new access keys A and B;
  • amount of a single debit of the balance when using the card for payment;
  • data is stored in a numeric block( value block), and mifare classic-specific operations for working with such blocks are used to access it.
Read more about each of the programs in this example. As before, I will not provide the full listing, nor will I provide the service code for working with the configuration file.

issue-card

Source code of the program: example-08/issue-card
This program performs personalization, i.e. prepares a new clean smart card for use in our system. To do this, we will write our own keys in a fixed sector of the card and the initial balance of 0 units in the selected numeric block. To manage the balance, we will use the Increment (top-up, refill) and Decrement (write-off, checkout) operations.

We will use the B key for "administrative" access: adding funds to the card, clearing (revoking) the card. We will use the A key exclusively for debiting and checking your balance. In this scenario, key B is "known" only by the control readers in the cash registers, and key A is recorded on all transport readers.

Initialization takes place normally: we read the necessary data from the config (the sector number, the block inside the sector, the original key A) and then try to authenticate with it to the last block of the sector. We are trying to read the sector trailer and check the access bits to it:

# try to read trailer block content
# CLA INS BlockMSB BlockLSB Le
apdu = 'FF B0 {:02X} {:02X} 10'.format(blockMSB, blockLSB)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('failed to read sector trailer data')
return 1
# check access condition, we should be able to change both access keys and write access bits using current Key A
# the only bits combination for this is `0 0 1`
access_conditions_bits = unpack_access_conditions_bits(response[6:9])
if access_conditions_bits[3] != [0, 0, 1]:
print('unsuitable access bits')
return 1
print('done')
In the util module.py we have defined functions for unpacking and unpacking access bytes: unpack_access_conditions_bitsand pack_access_conditions_bits. unpack_access_conditions_bits returns a list of four elements for each of the blocks. Each element also contains a list of three elements, where there can be either0, or 1. We require that the trailer block must initially have a set of access bits 0 0 1set .

If the check was successful, we generate a new trailer consisting of a new key A, new access bytes, and a new key B. For the selected block-value (where the balance is stored), set the access bits 1 1 0, for the trailer block — 0 1 1. These values allow us to use the B key as the main administrative key (you can use it to add funds to the card, as well as restore the selected sector to its original transport state), and the A key is used only for requesting a balance and debiting a fixed amount from the balance.

print('Initializing sector trailer ... ', end='')
# new access bit for trailer should be `0 1 1` (write Keys A and B using key B, read access bit with both keys,
# write access bits using Key B)
access_conditions_bits[3] = [0, 1, 1]
# new access bit for value block should be `1 1 0` (allow read and decrement with key A, all other operations
# with key B)
access_conditions_bits[value_block] = [1, 1, 0]
trailer_bytes = toBytes(key_a) + pack_access_conditions_bits(access_conditions_bits) + [0] + toBytes(key_b)
# write bytes to trailer block
# CLA INS BlockMSB BlockLSB Lc DATA
apdu = 'FF D6 00 {:02X} 10 {}'.format(value_sector * 4 + 3, toHexString(trailer_bytes))
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('sector trailer block write failed')
return 1
print('done')
To write the entire block value, we use the APDU with instructions 0xD6. Let me remind you that like other commands for working with Mifare cards, these instructions are implemented inside the reader (in our case, this is ACS ACR122U) and therefore they may differ for different devices. Usually, they are all described in the reader's documentation. For our service, you can view them here. The instruction code 0xD6command is described in section 5.4. Update Binary Blocks.

❈ ❈ ❈

After recording the trailer, we need to re-authenticate, this time with a new B key. After authentication, we initialize the numeric block and write 0 there.

print('Initializing value block ... ', end='')
balance = 0
nb = toHexString(list(balance.to_bytes(4)))
# write balance (0) to value block
# CLA INS P1 P2 Lc VB_OP ValueMSB...LSB
apdu = 'FF D7 00 {:02X} 05 00 '.format(value_sector * 4 + value_block) + nb
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('value block write failed')
return 1
print('done')
To write a numeric block, use the command with the instruction code0xD7, which is described in section 5.5.1. Value Block Operation of the reader documentation mentioned above. The command accepts the block number in the P2 field, and five bytes are passed in the data block (the Lc field contains the value 0x05).: first, the operation code (in our case, it 0x00is a value entry) and then an integer numeric value encoded as four bytes (big endian). To convert a number to a list of bytes, we use the following code:list(balance.to_bytes(4)).

refill-card-balance

In this program, we add a fixed amount to the card balance. To add funds, we use the standard Mifare Increment command, and for authentication, we use the B key.

# refill balance, add 50 units
n = 50
print('Incrementing balance by {} units ... '.format(n), end='')
nb = toHexString(list(n.to_bytes(4)))
# CLA INS P1 P2 Lc VB_OP ValueMSB...LSB
apdu = 'FF D7 00 {:02X} 05 01 '.format(value_sector * 4 + value_block) + nb
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('value block increment failed')
return 1
print('done')
For the increment, we use the same instruction 0xD7, only we specify it as the opcode 0x01, and we pass the value for the increment in the last four bytes of the data block in the same way.

check-card-balance

In this program, we authenticate with the key A and read the balance from the value block:

# read balance
blockLSB = value_sector * 4 + value_block
# CLA INS P1 P2 Le
apdu = 'FF B1 00 {:02X} 04'.format(blockLSB)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('authentication failed for trailer block of sector {:02X}'.format(value_sector))
return 1
print('new balance is {} units'.format(int.from_bytes(bytes(response))))
The instructions for this command - B1- are also described in the reader documentation in section 5.5.2. Read Value Block. There everything is simple, in the field P1 we pass0x00, and in the field P2 — the number of the data block. As a result, we get a value encoded in four bytes, and we use the code to reverse convert it to an integer value int.from_bytes(bytes(response)).

checkout

Source code of the program: example-08/checkout
In this program, we charge the card a fixed cost of the "trip"with each application. To simulate the operation of a transport reader, we run the code for processing the card in an infinite loop, and now the amount will be "debited" from the card every time it is brought to the reader. If there is not enough money on the card, a corresponding warning is displayed.

For this approach with an infinite loop to work, you need to slightly change the object initialization scheme: we specify an additional argument newcardonly=Trueso that the call cardservice = cardrequest.waitforcard()waits for the card to be removed from the reader first.

cardrequest = CardRequest(newcardonly=True, timeout=None, readers=[reader])
The rest is the same: we read the balance and check that it is greater than or equal to the debited amount. If everything is fine, then we perform the decrement operation:

print('Debit {} units from your balance ... '.format(single_checkout_value), end='')
nb = toHexString(list(single_checkout_value.to_bytes(4)))
# CLA INS P1 P2 Lc VB_OP ValueMSB...LSB
apdu = 'FF D7 00 {:02X} 05 02 '.format(value_sector * 4 + value_block) + nb
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('value block decrement failed')
continue
print('done')
Decrement is also performed via a command with an instruction 0xD7, only the operation code is now specified 0x02.

revoke-card

Source code of the program: example-08/revoke-card
This program clears the sector we use, resets keys and access bits to the transport state. Here, too, everything is similar: we authenticate with the B key, create a transport configuration for the block with the trailer, write zeros to the data block, and write the block with the trailer.

# fill data block with zeroes
block_bytes = '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'
# write bytes to trailer block
# CLA INS BlockMSB BlockLSB Lc DATA
apdu = 'FF D6 00 {:02X} 10 {}'.format(value_sector * 4 + value_block, block_bytes)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('sector data block write failed')
return 1
print('done')

access_conditions_bits = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 1]
]
print('Restore transport state of sector ... ')
trailer_bytes = toBytes(original_key_a) + pack_access_conditions_bits(access_conditions_bits) + [0] + toBytes('FF FF FF FF FF FF')
# write bytes to trailer block
# CLA INS BlockMSB BlockLSB Lc DATA
apdu = 'FF D6 00 {:02X} 10 {}'.format(value_sector * 4 + 3, toHexString(trailer_bytes))
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('sector trailer block write failed')
return 1
print('done')

Theory: Mifare Ultralight cards.

Mifare Ultralight cards are based on very cheap chips and are designed to store a small amount of data (40-144 bytes). The brand and specifications are owned by NXP.

At the moment, the Mifare Ultralight family consists of the following chips::

MIFARE UltralightCode name MF0ICU1, EEPROM 512 bits (16 pages of 4 bytes each), no authentication, yes. At the moment, these chips are discontinued.MIFARE Ultralight NanoCodename MF0UN (H)00, EEPROM 448 (14 pages of 4 bytes), no authentication, backward compatible with MF0ICU1, EU.MIFARE Ultralight CCodename MF0ICU2, EEPROM 1536 bits (36 pages of 4 bytes), 3DES authentication, backward compatible with MF0ICU1 when working with the first 512 bits, yes.MIFARE Ultralight EV1Codename MF0ULX1, EEPROM 640 bits (version MF0UL11, 20 pages of 4 bytes) or 1312 bits (version MF0UL21, 41 pages of 4 bytes), password protection to prevent memory changes, backward compatible with MF0ICU1 when working with the first 512 bits, yes.
❈ ❈ ❈

The memory is divided into pages, each containing four bytes, and its structure is shown in the table below. Pages are numbered from scratch.

Pagebyte 00byte 01byte 02byte 03comments
00SN0SN1SN2BCC0UID
01SN3SN4SN5SN6UID
02BCC1internalLock0Lock1write lock in user bytes
03OTP0OTP1OTP2OTP3single write bytes
04custom bytes
...
XXcustom bytesthe total number of pages depends on the card type
Each card has a unique serial number (UID) of 7 bytes long, encoded in the first nine bytes of memory. In the diagram, the UID bytes are indicated by cells from SN0to SN6, BCC0BCC1and the check bytes Byte0 and Check Byte1 are stored in cells and, respectively (see ISO/IEC 14443-3). It also SN0encodes the chip manufacturer (Manufacturer ID, see ISO / IEC 14443-3 and ISO/IEC 7816-6 AMD.1), the value for NXP Semiconductors is highlighted 04, the rest of the codes can be viewed, for example, here.

Lock bits are encoded in the Lock0 and Lock1 bytes. They can be used to block pages from 03before 0Efrom being overwritten. After installation, these pages are blocked from being written and can only be accessed by reading. The bit representation of these two bytes is as follows:

L7L6L5L4LOTPBL15-10BL9-4BLOTP
L15L14L13L12L11L10L9L8
In bits L4 through L15, you can write 1to block writing to the corresponding page. The L OTP bit is responsible for blocking writing to a page 3with OTP bytes. The BL 15-10 bit is responsible for blocking bits L10 to L15, the BL 9-4 bit is responsible for blocking bits L4 to L9, and the BL OTP bit allows you to block writing to the L OTP bit.

After writing 1to any of these bits, it can no longer be changed, and the corresponding block remains locked for writing.

❈ ❈ ❈

In bytes OTP0.. OTP3(OTP stands for One-Time Programmable), you can write arbitrary bit values, but only once. After writing a value 1to one of the 32-bit registers, this register is blocked and cannot be changed further. Writing 0to a register doesn't change it.

Reading and writing to the card is done in blocks-pages. You can read and write one page at a time.

❈ ❈ ❈

In the data sheets for a specific card model, you can see which memory blocks are allocated for special data. For example, you can record a password or access key in them, and enable special features (for example, a connection counter). These blocks are located at the end. For example, for Mifare Ultralight, C is blocks numbered from 0x29(41) to 0x2F(47) inclusive.

In all versions of Mifare Ultralight, the first pages 0x04(4) to 0x0F(15) inclusive (numbering from zero) can be protected from rewriting, and page 3 is allocated for OTP (One-Time Programmable).

example-09: Practical example: using the Mifare Ultralight card.

⭐ For this example, you can only use a reader for contactless chip cards.
For tests, you can take a paper one-time Moscow Metro pass or buy Mifare Ultralight keychains/stickers. To detect the card type, you can use the program detect-picc-typeshown in the example example-04.

The goals of this example are:
  • demonstrate how data is encoded in the Mifare Ultralight card;
  • read and display the paginated memory contents;
  • mark pages blocked from recording and other information from the card.

read-mifare-ultralight

Print out the contents of the memory and mark the pages that are read-only. We will store the content of the pages in a variable pages. First, we will read the first page, if this does not work, then the reader does not have a Mifare Ultralight card.

# read first page to check type
# CLA INS P1 P2 Lc
apdu = 'FF B0 00 {:02X} 04'.format(page)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Read Binary failed, probably not Mifare Ultralight card, terminating.')
return 1
pages.append(response)
page += 1
print('done')
To read the page, we use the already familiar B0 Read Binary instruction. Authentication is not necessary, just immediately read a block of four bytes. If we were able to read it, we save this block in the list pagesand read the rest in an infinite loop until an error occurs:

while True:
# CLA INS P1 P2 Lc
apdu = 'FF B0 00 {:02X} 04'.format(page)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
break
pages.append(response)
print('.', end='', flush=True)
page += 1
Next, we extract blocks with the identifier from the read bytes (see the table from the previous section):

uid_bytes = pages[0][0:3] + pages[1]
And we also read the page numbers that are locked for writing (we also look at the table, how the bits are distributed to indicate pages):

lock_0 = byte_to_bin(pages[2][2])
lock_1 = byte_to_bin(pages[2][3])
locked_pages = {}
for x in range(5):
if lock_0[x] == 1:
locked_pages[7-x] = True
for x in range(8):
if lock_1[x] == 1:
locked_pages[15-x] = True
And as a result, we simply print out the contents of all pages with additional notes:

print('Page | Memory bytes | State ')
print('-----+--------------+---------')
for i,p in enumerate(pages):
print(' {:02X} | {} | '.format(i, toHexString(p)), end='')
if locked_pages.get(i, False) == True:
print('locked')
else:
print('')
The result of the program on a paper travel card from the Moscow metro looks like this:

% ./read-mifare-ultralight
Connected reader: ACS ACR122U PICC Interface
Waiting for Mifare Ultralight card ...
Card connected, checking card, please wait ... done
Reading pages ................... 20 pages
UID: 34ABFAA114EC07
OTP bits: [1, 1, 1, 1, 1, 1, 1, 1] [1, 1, 1, 1, 1, 1, 1, 1] [1, 1, 1, 1, 1, 1, 1, 1] [1, 1, 1, 1, 1, 1, 0, 0]
Lock bits: [1, 1, 1, 1, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0]
Page | Memory bytes | State
-----+--------------+---------
00 | 34 AB FA ED |
01 | A1 14 EC 07 |
02 | 5E 00 70 08 |
03 | FF FF FF FC |
04 | 45 D9 BC 20 | locked
05 | F6 C7 2A 00 | locked
06 | 81 E0 38 40 | locked
07 | 00 B1 E0 01 |
08 | 00 05 A1 40 |
09 | 00 00 00 00 |
0A | A6 7B 53 47 |
0B | 81 E0 38 40 | locked
0C | 00 B1 E0 01 |
0D | 00 05 A1 40 |
0E | 00 00 00 00 |
0F | A6 7B 53 47 |
10 | 00 00 00 00 |
11 | 00 00 00 00 |
12 | 00 00 00 00 |
13 | 00 00 00 00 |

Theory: NFC tags

NFC tags, also known as NFC cards, are devices that meet the ISO/IEC 14443 standards and simultaneously meet the specifications of the NFC Forum, a community of organizations for the unification and standardization of the use of NFC (Near Field Communication) wireless technologies. To simplify matters completely, NFC devices are designed to transmit structured data (described in the NFC Forum specifications) via a radio channel, for example, to smartphones. Or to exchange information between smartphones and readers. The tag can contain a password for the Wi-Fi network, Bluetooth connection parameters, additional product data, website address, payment information, and so on. Nintendo uses NFC tags to produce Amiibo companion figurines for its games. Sometimes such devices without their own power supply are called passive tags.

A smartphone can also act as an NFC device, as it happens when using payment services such as Google Pay. Such devices are called active tags. The division into passive and active tags is rather conditional, since inside the NFC tag there may be a rather complex chip that can modify the stored data even during a read operation, for example, increase the counter for the number of card reads.

All aspects of using NFC are described in the NFC Forum technical specifications, which, unfortunately, are not public, but you can find them on the Internet if you want.

❈ ❈ ❈

In a simplified way, the NFC stack can be described by the following scheme::

NFC tech stack


A more complete diagram can be found on Wikipedia: NFC Protocol Stack. png.

❈ ❈ ❈

NFC as a specification is based on ISO/IEC 14443, FeliCa and other standards. Currently, there are the following types of NFC devices based on the type of RF interface used (all of them operate in the 13.56 MHz band)::

  • NFC A, based on Type A from the ISO 14443-3 standard
  • NFC B, based on Type B from the ISO 14443-3 standard
  • NFC F, based on JIS 6319-4 (Japanese FeliCa standard, developed by Sony)
  • NFC V, based on ISO 15693 (vicinity cards, with a much longer reading distance, up to about one and a half meters)
The NFC Forum specifications currently describe five types of NFC tags::

NFC Type 1 TagNFC A-based tags are the simplest and cheapest, but from 2021 this type is excluded from the NFC Forum specifications in favor of the NFC Type 2 Tag. The memory is divided into blocks of 8 bytes, with a total of 15 blocks or more.NFC Type 2 TagNFC A-based tags differ from NFC Type 1 Tags in the presence of mechanisms for protecting against conflicts and write collisions. At the moment, this is the most common type of NFC tags. The memory is divided into blocks of 4 bytes, with a total of 16 blocks or more.NFC Type 3 TagTags based on NFC F (FeliCa). The memory is divided into blocks of 16 bytes, the number of blocks is not fixed, there is no direct addressing to the blocks, instead grouping by service is used.NFC Type 4 TagNFC A or NFC B-based tags. There is no direct memory access. Instead, the ISO Data Exchange Protocol (ISO-DEP) and a set of APDU commands according to ISO/IEC 7816-4 are used for data exchange.NFC Type 5 TagNFC V-based tags.
❈ ❈ ❈

The specification defines for each label type exactly how memory is organized, what data should be stored in which cells, and what commands should be implemented for the described operations. For example, the Mifare Ultralight cards described in the previous sections are initially compatible with NFC Type 2 Tag and you can use the commands described above to access data.

Mifare Classic cards are not initially compatible, but NXP in a separate document (AN305) describes how to save NFC data to these cards. The application can use this document to implement support for Mifare Classic cards and then use them as regular NFC tags.

NXP also has a family of NFC chips NTAG, which were developed specifically according to the NFC Forum specifications: NTAG 213, NTAG 215, NTAG 216, NTAG 223, NTAG 424 DNA and others. The chips differ in the size of available memory, and also have additional features such as password protection of data access.

❈ ❈ ❈

The key element in the NFC Forum model is the NDEF data Exchange format (short for NFC Data Exchange Format). The main element of an NDEF is an NDEF message, which is a binary container consisting of one or more NDEF records. An NDEF record stores typed and structured payload data. Usually, there is only one data element in one record. For example, a single NDEF message can describe a restaurant and consist of three entries: the restaurant's website URL, location (geographical coordinates in geolocation data format), and a text description. Each record has its own standard type, which allows third-party applications to interpret them correctly.

Working with passive tags (for example, NTAG) is similar to this scenario:

  • the placemark (card) is brought to the reader;
  • the reader (and the software behind it) determines the card type. If this type is supported, then the algorithm for working with it is selected;
  • the algorithm determines whether the card contains NDEF messages. If it does, it reads them and passes them to the NDEF message processing algorithm;
  • if the NDEF message processing algorithm understands the format and purpose of the received data, then it is passed on for processing.
It is important to note here that at each stage, information is sequentially "unpacked", much like it happens when processing network data in the seven-level ISO/OSI model. Thanks to this approach, you can write isolated, independent pieces of code that don't know anything about the semantics of the data they work with, and their goal is to decompress them and pass them on to the next level of processing.

For example, for the Mifare Ultralight card, the NDEF message bytes are distributed among the page bytes in a special way, and the task of the reading algorithm is to collect these bytes and form a correct NDEF message. When writing in the same way, the algorithm distributes the NDEF message bytes to the card page bytes and writes them. Specifically, the Mifare Ultralight card complies with the NFC Type 2 Tag specification, so the algorithm is simple. But for Mifare Classic cards, you need to use the already mentioned AN305 document for conversion, and not the NFC Forum specification. In this case, the output of both algorithms is the same NDEF object, which is passed for further processing. In fact, Mifare Classic-based tags are a separate type in addition to the five described in the NFC Forum specifications.

❈ ❈ ❈

But back to the data. The NDEF message has the following structure::

NDEF Message structure


The entire message consists of consecutive NDEF entries, each of which begins with a record header that specifies the length, type, and other parameters of the entry. The header consists of the following fields::

TNF & FlagsBit flag set and TNF value:MB, Message BeginME, Message EndThe MB and ME flags determine the position of this record within the entire message: if the record is the first, then the MB flag is set to 1, if the record is the last, then the ME flag is set. If there is only one entry in the message, then both flags are set.CF, Chunk FlagThis flag is set if the payload is split into multiple records, and is set to 1 in all records except the last one. In practice, this flag is almost never used.SR, Short RecordIf the SR flag is set to 1, the Payload length field will be 4 bytes, otherwise it will be 1 byte.IL, ID LengthIf the IL flag is set to 1, the header will have the ID length field, otherwise this field will not be present.TNF, Type Name Formatthe value in these three bits determines the structure of the Payload type field and how to interpret it:0x00 — the Payload type field is empty0x01 — NFC Forum well-known type, NFC record type definition (NFC RTD), the Payload type field contains one of the values (as a string): T- Text, U- URI, Sp- Smart Poster, ac- Alternative Carrier, Hc— Handover Carrier, Hr— Handover Request, Hs— Handover Select.0x02 - Media-type, in the Payload type field, enter a value in the RFC 2046 format, for example, application/octet-stream.0x03 — Absolute URI, the Payload type field contains a URI in the RFC 3986 format.0x04 - NFC Forum external type, the Payload type field contains a URN according to RFC 2141, but packed in a special way.0x05 — Unknown, means that the payload type is unknown.0x06 — Unchanged, this value is used in the second and subsequent chunks of chunked records (i.e., for records that have the CF flag set).0x07 - reserved for future use.Type lengthThis field is 1 byte long and contains the length of the Payload type blockPayload lengthThis field (1 or 4 bytes in size, depending on the SR flag) records the length of the block with bytes of useful data.ID lengthThis field records the length of the Payload ID field. The presence of this field is determined by the IL flag.Payload typeThis field records the data type in the Payload block with useful data. The value recorded here must be interpreted according to the value of the TNF parameter.Payload IDThis field records the identifier (ID) of the block with useful data.
Usually, when writing code, you don't need to manually parse NDEF messages; this is much better done by the corresponding libraries, for example, ndeflib.
NDEF messages can be used in two modes: passive, when data is read or written from the tag; and peer-to-peer (P2P), when data is exchanged between two NFC devices in request-response mode. In the second case, the Simple NDEF Exchange Protocol (SNEP) is used together with the Logical Link Control Protocol (LLCP), but I will not write about these protocols in this article, this is a big topic for a separate article.

Theory: NFC Type 2 Tag

At the protocol level, NFC Type 2 Tag tags are partially compatible with Mifare Ultralight cards, but to use such a card as a tag, its memory must be marked in a special way so that the software understands where to look for NDEF records and metadata. The memory structure is as follows (according to the NFCForum-TS-Type-2-Tag v1.1 specification):

Blockbyte 00byte 01byte 02byte 03comments
00Internal0Internal1Internal2Internal3UID/Internal
01Internal4Internal5Internal6Internal7Serial Number
02Internal8Internal9Lock0Lock1Internal / Lock
03CC0CC1CC2CC3Capability Container (CC)
04Data0Data1Data2Data3Data
05Data4Data5Data6Data7Data
...
0FData44Data45Data46Data47Data
...
n....Data
...
.....Lock/Reserved
.....Lock/Reserved
k....Lock/Reserved
The memory consists of blocks, each block has four bytes, the minimum number of blocks is 16, with numbers from 0x00(0) to 0x0F(15). If there are more than 16 blocks, then blocks with numbers from 4 to k are allocated for user data, and blocks with numbers from k+1 to n are reserved for additional configuration. Reserved blocks can be located in any part of memory, starting from block 16, not necessarily at the end; their position is determined through special configuration bytes in blocks with user data. But usually they are always at the end of the card, and access to them is blocked at the hardware level of the label.

The Capability Container block stores information about the label's capabilities. These four bytes are One-Time Programmable (OTP). That is, after writing any bit field to 1, it remains there forever and cannot be changed back to 0. For an NFC tag, the structure of these bytes is as follows:
CC0CC1CC2CC3
E1PLATFORM VERSIONT2T DATA AREA SIZEACCESS CONDITION
The CC0 byte contains a fixed value 0xE1that indicates that this is an NFC Type 2 Tag.

The CC1 byte records the specification version according to which the label is made. For version 1.0, it is written there 0x10, for version 1.1 — 11.

The CC2 byte encodes the size of the memory area allocated for user data. To get the size in bytes, multiply the value from there by 8. The memory for user data starts with a block with a number 0x04.

The CC2 byte encodes the access conditions for the CC block and the user data block. The upper four bits encode read access (0 — read without restrictions), the lower four bits encode write access (0x0 — write without restrictions 0xF— - write is prohibited). The remaining values are reserved for future or proprietary use. In other words, the labels of individual manufacturers may use different values there to indicate some proprietary access conditions.

Here's what CC bytes look like for the NTAG215 label (135 blocks of 4 bytes each)::

CC0CC1CC2CC3
E1103E00
version 1.03E=62write and read allowed
496 bytes are allocated for user data (0x3E = 62, 62 × 8 = 496), these are 124 blocks, with a total of 135 blocks on the label. The first four blocks are reserved, meaning that 124 blocks are available for use, starting with the block with the number 0x04and ending with the block with the number 0x81, while the last 5 blocks are reserved for the label configuration.

Reserved blocks can contain dynamic lock bytes. They work on the same principle as bytes Lock0 and Lock1 from a memory block 02, that is, they allow you to permanently lock certain memory segments and make them read-only.

Reserved blocks also encode other functionality that is described in the data sheets for a specific tag type. This can be a password to block writing, a complete block of access to the label even for reading (KILL SWITCH), additional identification data, and so on.

❈ ❈ ❈

Inside the tag's user-accessible memory area, information is stored as TLV objects, which I will discuss in more detail later in the section about microprocessor cards. NFC uses a simplified version. TLV stands for Tag-Length-Value and denotes how key-value structures are encoded in binary format. The key is an integer, and the value is a set of bytes. In a byte representation, first there is a single byte to encode the tag, then there is a set of bytes to encode the length, and then a chain of bytes of this length.

The length can be encoded either as a single byte, then you can encode the value from 0x00to 0xFEinclusive. And if the length value is greater than 254, then the length is encoded in three bytes, where the first one is always0xFF, and the other two encode the value from 0000to FFFEinclusive.

This approach allows you to decode structured data in one pass, even without knowing its internal structure.

Here are some examples of TLV encoding of objects.

01 03 AA BB CC
Here01, a 3-byte value is encoded under the tag AA BB CC.

02 FF 01 05 10 20 30 ... 00 00
Here02, a value with a length 01 05of 261 bytes is encoded under the tag 10 20 30 ... 00 00.

03 00
Here0x03, a zero-length byte array is encoded under the tag, i.e. empty.

When encoding TLV objects in NFC, they must start at the beginning of the user data block (with a number 0x04) and be placed sequentially after each other without intermediate bytes.

❈ ❈ ❈

The NFC Type 2 Tag specification defines the following TLV objects::

Item nameTag codeDescription
NULL TLV00It can be used to separate and align TLV objects
Lock Control TLV01Defines the position of bytes with block blocking flags
Memory Control TLV02Defines the byte position with the coordinates of the blocked blocks
NDEF Message TLV03Contains an NDEF message in the format described above
Proprietary TLVFDContains proprietary information
Terminator TLVFEIndicates the end of a segment with TLV objects
The Lock Control TLV and Memory Control TLV objects must always be placed in front of the NDEF Message TLV and Proprietary TLV objects.

There should be no other label data elements after the Terminator TLV object.

The Terminator TLV and NULL TLV objects consist of only one byte, meaning that they do not contain a block with a length and value.

If objects with other tags that are not described in the specification are encountered during data decoding, they should be ignored.

The method of encoding data in the NFC Type 2 Tag specification is very difficult to implement, since data can be written to memory with breaks in the form of reserved byte segments (defined by the Lock Control TLV and Memory Control TLV objects), through which real data segments must "jump" when reading or writing. Therefore, the code for working with memory will be quite complex, confusing, and possibly not very correct in some places. But in practice, blocks with reserved data and lock bytes are usually located in the last blocks of memory, after they are allocated for application use, so a chain of TLV objects is simply written to the beginning of the section sequentially and read in the same way.

❈ ❈ ❈

In the examples below, I will demonstrate how to work with NFC tags via PC/SC. Since we do not use direct access to the reader, we cannot use libraries such as libnfc or nfcpy, instead we will perform all operations exclusively via PC/SC commands. This greatly limits the options, but many operations are still available. I limited myself to NFC Type 2 Tags only, as they are the most accessible ones.

example-10: Operations with the NFC Type 2 Tag

In this example, we'll look at reading and writing NFC Type 2 Tags. Any NTAG 213/215/216 tags will do, I recommend buying a dozen or more at once, it will be useful for the future.

nfc-type2-read

Source code of the program: example-10 / nfc-type2-read
First of all, we need to make sure that the reader has the right type of NFC tag. To do this, we read the block with the number 3 (Capability container) and check that its first byte is equal to0xE1:

# read block 0x03 - Capability container
block = 3
# CLA INS P1 P2 Lc
apdu = 'FF B0 00 {:02X} 04'.format(block)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Read Binary failed, probably not NFC Type 2 Tag.')
return 1

# check capabilities
if response[0] != 0xE1:
print('No NDEF container capability.')
print('Capability container bytes:', toHexString(response))
return 1
If everything is correct, then we read the entire tag memory in one flat array block by block:

block = 0
memory = []
while True:
# CLA INS P1 P2 Lc
apdu = 'FF B0 00 {:02X} 04'.format(block)
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
break
memory.extend(response)
block += 1
To read blocks, we use the standard Read Binary command with the instruction code B0. At the data reading level, labels of this type are compatible with Mifare Ultralight cards.

Next, we display general information about the placemark (memory size, number of blocks, etc.):

# print spec version, take most significant nibble (major version) and least significant nibble (minor version)
spec_version_byte = memory[3 * BLOCK_SIZE + 1]
print('Spec version: {}.{}'.format(msn(spec_version_byte), lsn(spec_version_byte)))

# print memory info
data_memory_size = memory[3 * BLOCK_SIZE + 2] * 8
print('Total memory: {} bytes in {} blocks'.format(len(memory), len(memory) // BLOCK_SIZE))
print('User data memory size: {} bytes'.format(data_memory_size))
print('User data blocks: {:02X} to {:02X}'.format(4, 3 + data_memory_size // BLOCK_SIZE))
print('Tag configuration blocks: {:02X} to {:02X}'.format(4 + data_memory_size // BLOCK_SIZE, len(memory) // BLOCK_SIZE - 1))

wac_byte = memory[3 * BLOCK_SIZE + 3] # wac - write access conditions
print('Read access condition: {}'.format(msn(wac_byte)))
print('Write access condition: {}'.format(lsn(wac_byte)))
In the global variableBLOCK_SIZE, we have a block size of 4. The lsn and msn functions return the lowest and highest nibbles (nibble), respectively, they are defined at the end of the file, but I don't give them here.

We count the amount of memory available for writing and save it to a variable data_memory_size. At the same time, we count and show what range of blocks this memory occupies.

Next, we parse user memory into TLV objects and display them on the screen:

data_memory = memory[4 * BLOCK_SIZE : 4 * BLOCK_SIZE + data_memory_size]

tlvs = tlvt2t.parse_bytes_list(data_memory)

for t in tlvs:
if t.tag == 1:
print('Lock Control TLV:')
bytes_from, bytes_to = tlvt2t.parse_lock_control_bytes(t.value)
print(' Bytes from {} to {}: {}'.format(bytes_from, bytes_to, memory[slice(bytes_from, bytes_to)]))
elif t.tag == 2:
print('Memory Control TLV:')
bytes_from, bytes_to = tlvt2t.parse_memory_control_bytes(t.value)
print(' Bytes from {} to {}: {}'.format(bytes_from, bytes_to, memory[slice(bytes_from, bytes_to)]))
elif t.tag == 3:
print('NDEF Message TLV:')
for record in ndef.message_decoder(bytearray(t.value)):
print(record)
For parsing, we use a simple self-written parser from the local tlvt2t module, here are its sources, it is very simple and creates a list of corresponding TLV objects based on the list of bytes. We are only interested in TLV objects with tag 3. These are NDEF messages. We decode them using the ndef module from the ndeflib library.

To decode Lock Control TLV and Memory Control TLV objects, we use the corresponding methods from the tlvt2t module, which I took from the nfclib project (https://github.com/nfcpy/nfcpy/blob/master/src/nfc/tag/tt1.py).

❈ ❈ ❈

Here is the result of running the program on an empty clean label NTAG 213:

Connected reader: ACS ACR122U PICC Interface
Waiting for card ...
Card connected.
NFC Tag Type 2 detected.
Spec version: 1.0
Total memory: 180 bytes in 45 blocks
User data memory size: 144 bytes
User data blocks: 04 to 27
Tag configuration blocks: 28 to 2C
Read access condition: 0
Write access condition: 0
Lock Control TLV:
Bytes from 160 to 162: [0, 0]
NDEF Message TLV:
The Tag configuration blocks section specifies the positions of memory blocks that store the tag configuration. These bytes are located outside of the blocks allocated for data storage.

The NDEF block is empty, so nothing is output. The Lock Control TLV object records the segment location with the bytes of the lock control flags, these are bytes at positions 160 and 161. In fact, they are located outside the available memory for storing data and match the corresponding bytes from the Mifare Ultralight specification.

You can use the program read-mifare-ultralightfrom the previous example to read the entire memory of this label and see what is there and where

nfc-type2-write

Source code of the program: example-10 / nfc-type2-write

For this example, an ACS ACR122U reader or a compatible one is required, since we will use a pseudo-APDU to send commands directly to the PN532 RFID module. You also need to take a clean label, without configured write restrictions.
In this program, we will write an NDEF block containing an arbitrary URL to the tag. Then you can use this tag on your smartphone to open the browser using this link. We will read the link address from the first argument of the program.

I must say right away that this example is very simple and does not take into account labels where bytes with lock flags and reserved segment addresses are located directly in available memory (in theory, this is a valid situation). We simply write the NDEF block in one continuous chunk. A clean tag like NTAG 213 or NTAG 215 is perfect for this.

According to the NFCForum-TS-Type-2-Tag specification, memory segments with NDEF messages must strictly follow the segments with Lock Control TLV (tag 1) and Memory Control TLV (tag 2). Therefore, we will first read all TLV objects from the label, take the first objects with tags 1 and 2, and add the generated TLV object with the NDEF message to them, and terminate all Terminator TLVs. Next, we convert the list of TLV objects to a list of bytes, add zeros to the size that is a multiple of the block size (4), and write this list of bytes to memory.

❈ ❈ ❈

The ACS ACR122U reader provides a special Direct Transmit APDU request for sending commands directly to the PN532 radio module (for a complete list of commands and other information, see the module's instructions).

The format of the Direct Transmit request is as follows:
CLAINSP1P2LcData
FF000000Payload lengthPayload

The sent command is encoded in the Payload field as follows (according to the specification from the PN532 instruction):
D4Command ByteCommand Data
  • 0xD4 - fixed value, indicates the data frame to be sent to the module
  • Command Byte-code (one byte) of the command
  • Command Data — data in a command-specific format

If the module processed the command and returned the result, the response is returned with a status word 90 00and a block of bytes in the following format:
D5Command Check ByteResponse Data
  • 0xD5 - fixed value, indicates the data frame sent from the module
  • Command Check Byte-returns the Command Byte value from the request plus one
  • Response Data — response data in a command-specific format, including the status of whether the command was completed successfully, and if successful, the response bytes are returned
⭐ In fact, the format of requests and responses to the module is somewhat different and contains a number of other fields, but the reader fills them in for us and deletes what is superfluous when the response is returned.
In this example, we will use only APDU Direct Transmit for all operations, they are encapsulated sequentially: reader command → radio module command → NFC tag command.

FF 00 00 00 09 D4 40 01 A2 04 03 10 D1 01

reader command (APDU)
|
CLA INS P1 P2 Lc radio module command
| | NFC tag command to write bytes `03 10 D1 01` to block `04`
| | |
FF 00 00 00 09 D4 40 01 A2 04 03 10 D1 01
❈ ❈ ❈

We will generate all APDU requests to the radio module using a simple functionpn532_c_apdu:

def pn532_c_apdu(cmd):
cmd_bytes = toBytes(cmd)
return 'FF 00 00 00 {:02X} D4 {}'.format(len(cmd_bytes) + 1, toHexString(cmd_bytes))
At the input, it receives a command for the module and returns the full pseudo-APDU for the reader. To convert the correct response from the module, we use another function pn532_r_apduthat cuts off the first two bytes with the D5 frame code and the instruction code control byte:

def pn532_r_apdu(b):
return b[2:]
The result is a byte array, the first element of which contains the status of command execution for almost all commands.

The command for communicating with the radio module (hereinafter referred to as the module command) is called InDataExchange and is described in Section 7.3.8 of the module instructions) PN532. The command code consists of a byte 0x40and two arguments, the first of which is always in our case0x01, and the second is the actual command for communicating with the tag (they are described in the NFC specifications and datasheet of specific tags, I will continue to call them tag commands).

To read memory, we will use the READ tag command with the code 0x30from the NFCForum-TS-Type-2-Tag specification,, the command looks like30 XX, where XXis the block number starting from which you need to read 16 bytes (that is, each such request reads four blocks at once).

First, we will try to read the first 16 bytes of the label to extract general information about the label from the bytes of the Capability Container, first of all, the memory size. We write the memory to a list variable memory.

apdu = pn532_c_apdu('40 01 30 {:02X}'.format(block))
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Read Binary failed, probably not NFC Type 2 Tag.')
return 1
response = pn532_r_apdu(response)
memory.extend(response[1:])
if response[0] != 0:
print('Failed to read tag header.')
return 1
Here we check the command execution status. If the first byte response[0]is not zero, it means that something went wrong and you can't continue working.

When the first 16 bytes are read, we can allocate the memory size and total number of blocks to read from them. This is the same code as in the previous example:

data_memory_size = memory[3 * BLOCK_SIZE + 2] * 8
data_memory = memory[4 * BLOCK_SIZE : 4 * BLOCK_SIZE + data_memory_size]
total_blocks = data_memory_size // 4
Then we read the remaining memory in the same 16 byte requests:

while True:
if block >= total_blocks:
break
# read segments of memory using PN532-command InDataExchange (code 40 01) and NFC-command READ (code 30)
apdu = pn532_c_apdu('40 01 30 {:02X}'.format(block))
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Read Binary failed, probably not NFC Type 2 Tag.')
return 1
response = pn532_r_apdu(response)
if response[0] != 0:
break
memory.extend(response[1:])
block += 4
Next, we must parse the memory into TLV blocks, since we need to allocate the Lock Control TLV and Memory Control TLV objects, which should be located before the memory segments with NDEF messages. We select these objects and immediately add them to the final listwrite_tlvs:

tlvs = tlvt2t.parse_bytes_list(data_memory)
write_tlvs = []

for t in tlvs:
if t.tag != 1 and t.tag != 2:
break
write_tlvs.append(t)
Next, we generate an NDEF record based on the argument with the URL, from it an NDEF message (using the ndeflib library), from it a TLV object, which we write to the list write_tlvs, and end everything with a terminating TLV object:

url = sys.argv[1]
record = ndef.UriRecord(url)
message = [record]
ndef_bytes = b''.join(ndef.message_encoder(message))
write_tlvs.append(tlvt2t.TLV(0x03, list(ndef_bytes)))
write_tlvs.append(tlvt2t.TLV(0xFE, []))
tlv_data = tlvt2t.pack_tlv_list(write_tlvs)
The tlv_data variable contains a binary representation of the entire block of TLV objects, and we can now write it to the label. Since writing occurs in blocks, we first align the length to a multiple of 4:

pad_size = (BLOCK_SIZE - len(tlv_data) % BLOCK_SIZE) % BLOCK_SIZE
tlv_data.extend([0] * pad_size)
To write, we use the WRITE label command with the code0xA2, the command has two arguments: the block number and four bytes of the block content to write. We write data starting from the block with index 4. If the tag command execution status is not equal to zero, we display an error message, but we continue trying to write the following blocks.

for i in range(len(tlv_data) // BLOCK_SIZE):
chunk = tlv_data[i * BLOCK_SIZE : (i + 1) * BLOCK_SIZE]
block = 4 + i
apdu = pn532_c_apdu('40 01 A2 {:02X} {}'.format(block, toHexString(chunk)))
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print(f'Failed to update block {block}!')
return 1
response = pn532_r_apdu(response)
if response[0] != 0:
print('Failed to update block {:02X}'.format(block))
❈ ❈ ❈

The program is called like this:
% ./nfc-type2-write https://blog.regolit.com
Connected reader: ACS ACR122U PICC Interface
Waiting for card ...
Card connected.
Reading tag ... done
Writing tag ... done
After successful recording, you can test the tag on your NFC-enabled phone. When you hold it up to the tag, a browser should open with the address you recorded.

❈ ❈ ❈

Manufacturers of NFC tags usually add additional features to their devices in addition to those described in the tag specifications. Most often, this is password protection of access (for writing or reading/writing) to a specific memory area.

The NFCForum-TS-Type-2-Tag specification only provides for permanent blocking of individual memory regions from writing. You can completely block a label from writing by making it read-only.

Working with contact memory cards

Contact (i.e. with a contact pad) memory cards do not have internal program logic (like Mifare Classic cards) and require a reader that understands their signal protocol. In fact, they are not even "smart" cards and do not support APDU. Such cards are very cheap and were once used as a means of payment in payphones, for example. And now they sometimes continue to be used in hotel access control systems. The contents of the memory card are usually freely readable, but prior authentication via a secret PIN is required for writing. Cards can also be provided with PIN brute-force protection: after a small number of unsuccessful attempts, the card is blocked.

контактные By now, contact memory cards are almost out of use and are rarely used. However, using their example, it is very convenient to learn the basic principles of working with cards, readers, specifications and documentation.
The most popular chip for contact memory cards is the SLE family, originally developed by Siemens (later the division was separated into a separate company Infineon Technologies AG). Here are some popular chips in this family::

  • SLE 4418 — 1 KByte EEPROM with Write Protect Function
  • SLE 4428 — 1 KByte EEPROM with Write Protect Function and Programmable Security Code (PSC)
  • SLE 5518 — 1 KByte EEPROM with Write Protect Function (замена SLE 4418)
  • SLE 5528 — 1 KByte EEPROM with Write Protect Function and Programmable Security Code (PSC) (замена SLE 4428)
  • SLE 4432 — 256-Byte EEPROM with Write Protect Function
  • SLE 4442 — 256-Byte EEPROM with Write Protect Functionand Programmable Security Code (PSC)
  • SLE 5532 — 256-Byte with Write Protection (replacing SLE 4432)
  • SLE 5542 — 256-Byte with Write Protection and Programmable Security Code (PSC) (замена SLE 4442)
  • SLE 4440 — 64-Byte EEPROM with Write Protect FunctionSLE 4440and Programmable Security Code (PSC)
  • SLE 4441 — 128-Byte EEPROM with Write Protect FunctionSLE 4441and Programmable Security Code (PSC)

Another popular chip manufacturer is Atmel, with some of its chips:
  • AT24C02C — 2048 bit EEPROM
  • AT24C128C — 128 kbit EEPROM
  • AT88SC153 — 2048 bit EEPROM (64 bytes configuration zone, 192 bytes user data)
  • AT88SC1608 — 17408 bit EEPROM (128 byte configuration zone, 2048 bytes bytes user data)
❈ ❈ ❈

Technologically, contact memory cards comply with the standard ISO / IEC 7816-10: Electronic signals and answer to reset for synchronous cards, it defines the signal (electrical) protocol for synchronous cards, while the cards DO NOT comply with the standard ISO/IEC 7816-4, which describes protocols for data exchange. In other words, to interact with such a card, you must manually send and read electrical signals of a certain shape at certain contacts of the chip. The format of the signals and the commands encoded in them are described in the datasheets of each chip, for example, in the SLE 4432 datasheet.

Readers can, but are not required to, support different card types. This is done through pseudo-APDUs in the class CLA=FF(similar to Mifare Classic cards), and these APDUs are different for different readers and cards. For example, in the manual for the ACR38U-I1 reader, such commands are described for each of the supported cards in Section 9.3. Memory Card Command Set. To work with memory cards, you need to look at the reader's manual and the manual for the specific card type at the same time. There are no universal commands for all readers.

example-11: Working with a contact memory card SLE 5542

For this example, you can only use the ACR38U-I1 reader (or another one from the same manufacturer with the same command system) and the SLE5542/SLE4442 memory card.

The goals of this example are:
  • show how to read and write to a contact memory card via a reader;
  • show you how to read the card and reader specifications correctly.

First of all, you will need two documents that you will constantly check with:
The pseudo-APDUs we need for the SLE 5542 card are described in Section 9.3.6. Memory Card-SLE 4432/SLE 4442/SLE 5532/SLE 5542 ([ACR], page 47).

First of all, we need to understand what data is stored on the card and how. In [SLE], on page 7, the characteristics of the chip are described. We are interested in these features:
  • 256×8-bit EEPROM organization — that is, there are 256 bytes of memory in the EEPROM on the card;
  • Byte-wise addressing — byte-by-byte addressing;
  • Irreversible byte-wise write protection of lowest 32 addresses (Byte 0 ... 31) — the first 32 bytes in the EEPROM (with addresses from 0 to 31) can be protected from overwriting;
  • 32 × 1-bit organization of protection memory — 32 bits (i.e. 4 bytes) that are used in the previous paragraph to control overwrite protection (these bits are stored separately from 256 bytes of main memory and are called Protection memory in [SLE] terminology);
  • Data can only be changed after entry of the correct 3-byte programmable security code (security memory) — to write data, you need pre-authentication with a three-byte code (these bytes are stored separately from 256 bytes of main memory and are called Security memory in [SLE] terminology).
So, you can always read data from the card, no protection is provided for this. Out of 256 bytes, the first 32 can be protected from repeated overwriting by hardware (permanently, you can't remove the protection). In general, any record requires authentication with a three-byte code (pin). PIN protection is a feature of the SLE 5542 and SLE 4442 chips, but the SLE 4432 and SLE 5532 chips do not have such protection.

The structure of the first 32 bytes of the SLE4442/SLE5542 EEPROM cards is fixed and is described in detail in [SLE], section 2.8 Coding of the Chip. Some of these fields are protected from being overwritten by the chip manufacturer.

sle-card-read

Source code of the program: example-11/sle-card-read
In this program, we will read the contents of all 256 bytes of the EEPROM, the contents of Protection memory, and the authorization error counter.

First, we need to tell the reader exactly which card we want to use. This is done by the instruction described in [ACR] in section 9.3.6.1. SELECT_CARD_TYPE:

# CLA INS P1 P2 Lc DATA
apdu = 'FF A4 00 00 01 06'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Select failed')
return 1
This instruction is MANDATORY when working with the card. If you don't follow it, the result will be unpredictable.

❈ ❈ ❈

A pseudo-APDU for reading memory is described in [ACR], in section 9.3.6.2. READ_MEMORY_CARD. But there is one small problem with it — they can read a maximum of 255 bytes, since in one byte you can specify a maximum of 0xFF, that is, 255. Therefore, we can read all 256 bytes in at least two passes: first the first 32, and then the remaining 224, after which we merge the two arrays into one:

# Instruction "9.3.6.2. READ_MEMORY_CARD"
# Read all 256 bytes in two passes.
# First read 32=0x20 (in "Le" field) bytes starting with address 0x00 (in "P2" field)
# field "P1" is ignored

# CLA INS P1 P2 Le
apdu = 'FF B0 00 00 20'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Cannot read card data')
return 1
eeprom_data = response

# Then read remaining 224=0xE0 (in "Le" field) bytes starting with address 0x20 (in "P2" field)
# field "P1" is ignored

# CLA INS P1 P2 Le
apdu = 'FF B0 00 20 E0'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Cannot read card data')
return 1
eeprom_data.extend(response)
Print all bytes in blocks of 32:

print('EEPROM memory:')
for i in range(0, len(eeprom_data), 32):
chunk = eeprom_data[i:i+32]
print(' ', toHexString(chunk))
❈ ❈ ❈

Reading the contents of Protection memory via pseudo-APDU from section 9.3.6.4. READ_PROTECTION_BITS:

# Instruction "9.3.6.4. READ_PROTECTION_BITS"
# read 0x04 (in "Le" field) bytes of Protection memory
# fields "P1" and "P2" are ignored

# CLA INS P1 P2 Le
apdu = 'FF B2 00 00 04'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Cannot read card data')
return 1
prb_data = response
However, we don't just want to show the bytes read, but we want to do it in a table format to show which of the first thirty-two bytes of the EEPROM is available for rewriting.

The result PRBDataconsists of four bytes, denoted as PRB1, PRB2, PRB3, PRB4. According to [ACR] (page 50), the bits in these bytes are cardped to the first thirty-two bytes of the EEPROM as follows:

PRB1 │ PRB2 │ PRB3 │ PRB4
═══════════════════════╪══════════════════════════════╪═══════════════════════════════╪═══════════════════════════════
P8 P7 P6 P5 P4 P3 P2 P1│P16 P15 P14 P13 P12 P11 P10 P9│P24 P23 P22 P21 P20 P19 P18 P17│P32 P31 P30 P29 P28 P27 P26 P25
Therefore, we get this code (0it means that the byte cannot be overwritten 1— but it can be):

print('Protection memory bits:')
print(' ', end='')
for x in range(32):
print('{:02X} '.format(x), end='')
print('')
protection_bits = [0 for x in range(32)]
for k in range(4):
b = prb_data[k]
for i in range(8):
addr = k * 8 + i
protection_bits[addr] = b & 1
b >>= 1
print(' ', end='')
for x in range(32):
print('{: 2X} '.format(protection_bits[x]), end='')
print('')
❈ ❈ ❈

We will also print out the value of the error counter. This command is described in section 9.3.6.3. READ_PRESENTATION_ERROR_COUNTER_MEMORY_CARD (SLE 4442 and SLE 5542):

# Instruction "9.3.6.3. READ_PRESENTATION_ERROR_COUNTER_MEMORY_CARD (SLE 4442 and SLE 5542)"
# read 0x04 (in "Le" field) bytes of Security memory (only EC value is returned)
# fields "P1" and "P2" are ignored

# CLA INS P1 P2 Le
apdu = 'FF B1 00 00 04'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Cannot read card data')
return 1
ec_data = response

print('EC: {:02X}'.format(ec_data[0]))
❈ ❈ ❈

Here's what it looks like to run this program on my new clean card:

% ./sle-card-read
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected.
EEPROM memory:
A2 13 10 91 FF FF 81 15 FF FF FF FF FF FF FF FF FF FF FF FF FF D2 76 00 00 04 00 FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
Protection memory bits:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
0 0 0 0 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1
EC: 07
As you can see, the first four bytes are protected from overwriting. They store data from the ATR. Another protected fragment - 81 15- contains ICM=81(IC Manufacturer Identifier, chip Manufacturer ID) and ICT=15 (IC Type, Card type).

sle-card-write

Source code of the program: example-11/sle-card-write
In this program, we will write something to the card.

First, we select the card using the SELECT_CARD_TYPE statement in the same way as in the previous program.

Next of all, we need to log in using a three-byte code (Programmable Security Code, PSC). On the new card, the PSC in hex form looks like this:FF FF FF. Each failed authorization attempt increases the Error Counter (EC), and after three consecutive errors, the card permanently blocks all attempts to log in and, accordingly, the ability to write something to the card. After successful authorization, the tag is reset.

Authorization via PSC is described in [ACR] in section 9.3.6.7. PRESENT_CODE_MEMORY_CARD (SLE 4442 and SLE 5542) (page 53):

# Instruction "9.3.6.7. PRESENT_CODE_MEMORY_CARD (SLE 4442 and SLE 5542)"
# write 3 bytes of PSC
# field "P1" is ignored

# CLA INS P1 P2 Lc DATA
apdu = 'FF 20 00 00 03 FF FF FF'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x07):
print('PSC auth failed')
return 1
Important point: we are comparing the status word (SW) not with the usual value 0x9000, but with 0x9007! This is the code for successful execution of this command.

Then we write the data, four bytes01 02 03 04, starting from the address64=0x40:

# Instruction "9.3.6.5. WRITE_MEMORY_CARD"
# write 4 bytes "01 02 03 04" starting with address 0x40 (in "P2" field)
# field "P1" is ignored

print('Writing data ... ', end='')
# CLA INS P1 P2 Lc DATA
apdu = 'FF D0 00 40 04 01 02 03 04'
response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Write memory failed')
return 1
print('done')
If we now read the card with the previous program sle-card-read, we will see the bytes we recorded:

% ./sle-card-read
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected.
EEPROM memory:
A2 13 10 91 FF FF 81 15 FF FF FF FF FF FF FF FF FF FF FF FF FF D2 76 00 00 04 00 FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
01 02 03 04 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
Protection memory bits:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
0 0 0 0 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1
EC: 07
❈ ❈ ❈

At this point, I finish the example. Behind the scenes are the following instructions::

  • 9.3.6.6. WRITE_PROTECTION_MEMORY_CARD-protection of certain bytes by changing Protection memory;
  • 9.3.6.8. CHANGE_CODE_MEMORY_CARD (SLE 4442 and SLE 5542) - change the PSC PIN code.
You can experiment with them yourself.

Theory: Industry standards

Earlier, I have repeatedly mentioned the international technical standards that smart cards must meet. Now I will tell you more about this.

The most important standard is the ISO/IEC 7816 family, which consists of more than ten parts describing various aspects of classic smart cards. Some of these standards have been officially translated into Russian and issued as State standards (in this case, I provide links to these translations).

Five standards define the physical and electrical characteristics of cards/interfaces:

ISO/IEC 7816-1: Cards with contacts — Physical characteristics
Official Russian translation: GOST R ISO / IEC 7816-1: cards with contacts. Physical characteristics.

Defines the physical characteristics of cards with contacts: size, strength requirements, and mechanical reliability.

ISO/IEC 7816-2: Cards with contacts — Dimensions and location of the contacts
Official Russian translation: GOST R ISO / IEC 7816-2: cards with contacts. Contact sizes and locations

Defines the size, location, and designation of external conductive contacts on the card.

ISO/IEC 7816-3: Cards with contacts — Electrical interface and transmission protocols
Official Russian translation: GOST R ISO / IEC 7816-3: cards with contacts. Electrical interface and transmission protocols

Defines the electrical interface and transmission protocols for asynchronous cards

ISO/IEC 7816-10: Electronic signals and answer to reset for synchronous cards
There is no official translation into Russian.

Defines the electrical interface and answer-to-reset (ATR) for synchronous cards.

ISO/IEC 7816-12: Cards with contacts — USB electrical interface and operating procedures
There is no official translation into Russian.

Defines the electrical interface and operating procedures for USB cards.

❈ ❈ ❈

Other standards in this family are independent of the specific physical interface technology.:

ISO/IEC 7816-4: Organization, security and commands for interchange
Official Russian translation: GOST R ISO / IEC 7816-4: Identification cards. Integrated circuit cards. Part 4. Organization, protection, and teams for sharing.

Defines the organization, security, and teams for sharing information. This is the most important standard for the industry, and it is the basis for all other standards.

ISO/IEC 7816-5: Registration of application providers
There is no official translation into Russian.

Defines how to use application identifiers, and also sets the regulatory framework for central registration of identifiers.

ISO/IEC 7816-6: Interindustry data elements for interchange
Official translation: GOST R ISO / IEC 7816-6: Identification cards. Integrated circuit cards. Part 6. Cross-industry data elements for sharing

Sets data elements that are used for cross-industry data exchange based on smart cards. Sets the name, ID, format, encoding, and reference to the data standard for each element.

ISO/IEC 7816-7: Interindustry commands for Structured Card Query Language (SCQL)
Official Russian translation: GOST R ISO / IEC 7816-7: Identification cards. Integrated circuit cards with contacts. Part 7. Cross-industry commands of the Structured Query Language for cards (SCQL)

Defines structured query language commands for cards

ISO/IEC 7816-8: Commands and mechanisms for security operations
Official Russian translation: GOST R ISO / IEC 7816-8: Identification cards. Integrated circuit cards. Part 8. Commands for data protection operations.

Defines cross-industry commands for the card that can be used for cryptography operations.

ISO/IEC 7816-9: Commands for card management
Official Russian translation: GOST R ISO / IEC 7816-9: Identification cards. Integrated circuit cards. Part 9. Commands for managing cards.

Defines cross-industry commands for managing the card and files. These commands cover the entire life cycle of the card, and therefore some commands can be used before the card is issued to the cardholder or after the card expires.

ISO/IEC 7816-11 Personal verification through biometric methods
Official Russian translation: GOST R ISO / IEC 7816-11: Identification cards. Integrated circuit cards. Part 11. Identity verification using biometric methods.

Sets cross-industry commands related to the security system and used for verification of identity by biometric methods in integrated circuit cards. It also defines the data structure and data access methods for using the card as a carrier of biometric reference data and/or as a device that allows you to perform identity verification using biometric methods (i.e., "on-card" matching).

ISO/IEC 7816-13: Commands for application management in multi-application environment
Official Russian translation: GOST R ISO / IEC 7816-13: Identification cards. Integrated circuit cards. Part 13: Commands for managing applications in a multi-application environment.

Defines commands for managing applications in a multi-application environment (i.e., several different applications are installed on the card). These commands cover the entire application lifecycle in a multi-application card on an integrated circuit; commands can be used before and after the card is issued to the cardholder.

ISO/IEC 7816-15: Cryptographic information application
There is no official translation into Russian.

Defines the application for working with cryptographic data: storage, receiving, processing, and authentication.

❈ ❈ ❈

The ISO/IEC 14443: Identification cards-Contactless integrated circuit cards-Proximity cards standard defines the physical characteristics, frequencies, signal interface, and data transfer protocol for contactless cards. It consists of four parts and all of them are officially translated into Russian.

❈ ❈ ❈

Near Field Communication — NFC. This technology is based on ISO / IEC 14443 and ISO / IEC 18000-3. ISO/IEC 18000 is the standard for RFID tags, and its Part 3 describes the technical parameters of the radio interface at a frequency of 13.56 MHz. To coordinate NFC development efforts, the NFC Forum consortium was created, as I wrote about above. There are also other organizations developing their own aspects of this technology.

The radio aspects of NFC are described by the following standards:

  • ISO/IEC 18092 / ECMA-340—Near Field Communication Interface and Protocol-1 (NFCIP-1)
  • ISO/IEC 21481 / ECMA-352—Near Field Communication Interface and Protocol-2 (NFCIP-2)
❈ ❈ ❈

Bank cards use the EMV standard, which I'll discuss in more detail in the following sections.

❈ ❈ ❈

In GSM telephony for SIM cards, technical standards of the European Telecommunications Standards Institute are used, in particular ETSI TS 102 221: Smart Cards; UICC-Terminal interface; Physical and logical characteristics.

❈ ❈ ❈

All of these standards are related and often refer to each other. If you are going to thoughtfully study the field of smart cards, then you will have to get out these standards and carefully study them. Also keep in mind that each specific card supports only a certain part of the standard, not the entire standard.

Theory: interaction with the microprocessor card

Mifare Classic or SLE5542 cards are memory cards, meaning they don't provide any additional logic other than reading and writing bytes. In this section, we will talk about other full-fledged microprocessor cards with the operating system and applications.

Examples of microprocessor cards:
  • bank plastic cards with a chip;
  • SIM cards for mobile phones;
  • electronic ID cards and passports;
  • cryptographic USB tokens (for example, yubikey or Rutoken).
All these cards are made and function in accordance with the standards of the ISO/IEC 7816 family, in particular, they allow you to use the APDUs described in ISO/IEC 7816-4 for communication.

The main reader for this and subsequent sections will be a card reader with a contact pad. Accordingly, we will need a variety of chip cards for experiments: credit cards, SIM cards, and so on.

⭐ Also note that when I refer to specific sections from the text of the ISO/IEC 7816 standard, I am referring to the 2020 edition. In previous editions (2005 and 2013), the numbering and structure of the text was different.

Working with data

Unfortunately, there is no universal method for accessing data on a smart card. Different types of cards require different approaches. The most common types of microprocessor-based smart cards today are chip-based bank plastic cards and SIM cards for GSM phones.

The main technical standard for bank cards is called EMV, in the first letters E uropay, M MasterCard, V isa. The documentation on it is available for free download from the official website. At the time of writing, the latest version of EMV is 4.3 and you can download the files from here: https://www.emvco.com/emv-technologies/contact/. Working with EMV cards will be covered in one big example later.

The current name of smart cards for GSM/UMTS is UICC (Universal Integrated Circuit Card), originally called SIM cards, but later the functionality was significantly expanded. The standard is being developed by ETSI (European Telecommunications Standards Institute), and documents are freely available from the organization's official website: https://www.etsi.org

Operating system on the card

The microprocessor card has its own operating system installed. It determines the card's behavior. Physically and electrically, the card can comply with ISO/IEC 7816-1 and ISO/IEC 7816-2 standards, but it can only partially implement ISO/IEC 7816-3 or ISO/IEC 7816-4.

Such an operating system is called COS (Chip Operating System). It consists of two parts: the basic part (implements basic low-level operations) and the domain-specific part (implements specific functionality, such as cryptography). However, this division is very conditional: initially, cards could perform only one function. Later, there were cards where several independent "applications" that implement completely different functions can run in parallel.

At the dawn of technology, each card manufacturer created its own proprietary operating system. Applications for each of these systems had to be written separately, and they usually came from the factory already installed on the card. With the development of technology, there is a need for a general-purpose COS: so that businesses can write their own applications and upload them to the card. This is how Java Card OS (from Sun Microsystems) and Multos (which came out of the banking and financial community) appeared. At the moment, these are the two main players in the COS market.

In addition, the GlobalPlartform organization was formed (http://www.globalplatform.org/), which standardizes a variety of tasks related to smart cards and their applications.

The actual application (for example, a banking application) runs on top of COS. ISO / IEC 7816 defines an application as follows: structures, data elements, and software modules required to perform certain functions. In fact, this is a set of files and programs united by a common task.

Data: files and applications

The program communicates with the card via the reader: the program sends commands (C-APDU) and receives responses (R-APDU). Their entire format is described in section 5 Command-response pairs of the ISO/IEC 7816-4 standard. I have already described it briefly in the section Structure of the command-response pair: this is a set of bytes consisting of four mandatory header bytes (CLA, INS, P1, P2), and several optional ones (Le, Lc, DATA). All types of C-APDU can be divided into 4 options: Option 1, Option 2, Option 3, Option 4.

The most significant bit of b8 in CLAdetermines whether the class belongs to it. If b8=0(0x00i.e.0x7F values from to inclusive), then this is an interindustry class and its further structure is described in the ISO/IEC 7816 standard. If b8=1(i.e. values from 0x80to 0xFFinclusive), then this is a proprietary class and its structure may be described somewhere else, in other standards or specifications. For an inter-branch class, the remaining bits in the byte CLAdefine different functions (for example, a logical channel or data encryption in a command), which are described in detail in the standard. By default, the byte is often CLAset to0x00, and if there is supposed to be some other value, this will be specifically specified.

Each branch defines its own commands and data formats, usually using a proprietary command class for such instructions CLA. For example, it is used for bank cards CLA=80, but not for SIM/UICC CLA=A0. And CLA=FFit is actively used in readers to generate queries that are converted by the reader into commands for specific cards.

❈ ❈ ❈

In general, the interaction of an external program with the card via the reader can be represented as a dialog consisting of C-APDU (command, APDU-command) and R-APDU (response, APDU-response). The card's microprocessor processes each command and generates a response. Some commands are described in ISO / IEC 7816-4, and the program that calls them expects a response of a certain structure. Other commands are described in industry standards with their own semantics. Very conditionally, all teams can be divided into two groups:

  • commands for working with static data recorded on the card: read, write, delete, search, and so on;
  • commands for performing operations on the card, such as generating a private key in a crypto token or generating a digital signature for a transmitted string of bytes.
Commands can change the card state. For example, to read a certain block of data, you need to send several APDUs sequentially. The order in which commands are executed also matters. You can't run an arbitrary command at an arbitrary time.

❈ ❈ ❈

The ISO / IEC 7816-4 standard defines a special logical model for accessing data on a card. They are represented by objects that are called files in the standard's terminology. A file in this model differs from a file in a desktop operating system. In other words, it is not an array of arbitrary bytes that can be read or written, but an object with certain properties that can be accessed via APDU commands. This object can be accessed in different ways, which are selected by the card manufacturers based on the full list of standards. For example, using an ID or name, I'll tell you more about what it is below.

Files (more precisely, as they are viewed through the reader) can form a hierarchical structure. For this purpose, ISO / IEC 7816-4 defines the following types::

elementary file / EF
An object with data that can't contain other objects. Two types of EF are defined:

internal elementary file / IEFdata in it is read and written exclusively by the card's operating system or card applications. For example, the IEF stores secret keys for cryptography. IEFS are not available for an external application.working elementary file / WEFdata in it can be read and written by an external application via a reader.short EF identifier / SFIA special type of identifier that is used only for elementary files and consists of a maximum of five bits (thirty possible values from 00001to 11110, the value 00000refers to the currently selected EF, the value 11111is not used). This ID allows you to read data from a file without first selecting it (i.e., without calling the SELECT statement, see below).
We will continue to work only with WEF.

dedicated file / DF
It is something like a container, DF can contain either elementary files (EF) or other designated files (DF). Informally, it is common to refer to DFS as directories. DF can also store data. All directly nested efs and dfs for a given DF must have different IDs.

master file / MFThis is the DF allocated in ISO / IEC 7816-4 as the "master" file in the hierarchy. The MF may not be present, but if it is, its ID is 3F 00.DF nameYou can assign a name to a DF — a byte array up to 16 bytes in size. You can use any byte in the name, not just ASCII letters. The name must be unique within the entire card.file identifier / FIDA byte array of two elements. The IDs of files that share a common parent must be different. The ID of the file and its parent must be different. IDs don't have to be globally unique across the entire card.
ISO/IEC 7816 File System


This MF, DF, and EF structure is often informally referred to as the ISO file system. Russian-language names like "assigned file" are official and are taken from the GOST R ISO standard:IEC 7816-4.

The described structure / hierarchy of objects is not mandatory and is given in the ISO/IEC 7816-4 standard only as an example. As a result, the card may contain a full-fledged file hierarchy with MF at the head and simultaneously contain DF or EF that are not in it. Often there is no MF at all, and the client software must know the IDs or names of the DFS in advance in order to access them directly.

❈ ❈ ❈

application dedicated file / ADFA special DF type that contains all the EFS and DFS of a particular card application in the tree. The ADF may be located outside the MF hierarchy.application identifier / AIDThe application ID is an ADF name (i.e., a byte string between 5 and 16 bytes in size).
AID has a specific structure, the first five bytes form the RID — Registered Application Provider Identifier, and the remaining bytes form the PIX-Proprietary Application Identifier Extension. An Application Provider is usually a firm, corporation, or other similar entity. An Application Provider can get a unique RID after contacting an authorized organization. The procedure for these procedures is described in ISO / IEC 7816-5. In fact, RID is an identifier of an organization that can create its own AIDS based on it, adding arbitrary bytes (PIX) to the end. For more information about the AID/RID/PIX structure, see section 8.2.1.2 Application identifier of the ISO/IEC 7816-4 standard.

Example: An application for working with Visa on a bank card has AID=A0 00 00 00 03 20 10, at RID=A0 00 00 00 03the same time (the VISA organization's RID), andPIX=20 10. Another example,AID=A0 00 00 06 58 10 10, RID=A0 00 00 06 58is the ID of the MIR payment system, PIX=10 10. A large list of RIDS can be found here: https://www.eftlab.com.au/knowledge...gistered-application-provider-identifiers-rid.

It is important to understand that the card application itself is hidden from the external client and is accessible only through APDUs, some of which are cross — industry (i.e. defined in ISO/IEC 7816), and some are proprietary.

Usually, the end user of the card does not have the ability to create or delete files. And in general, they should not be perceived as static byte objects, they are just an interface for interacting with the smart card application.

❈ ❈ ❈

Working with files (EF/DF) occurs by changing the current state of the card. This operation is called SELECT. The SELECT statement is described in section 11.2.2 SELECT command of ISO / IEC 7816-4, and the procedure and logic of the process are described in Section 7.3 Structure selection.

SELECT binds the file to the channel specified in the byteCLA. This is usually zero, but there are cards that support multiple channels. The channel number is set by bits b1 and b2 in CLA. However, this is already an advanced topic and in reality this information will not be useful to you, since everyone uses the default channel.
Byte INS— A4. The semantics P1of the and parameters P2are described in detail in the standard. Once selected, you can send APDU commands to work with this file.

To select, you can use FID, DF name, EF/DF identifier relative to the currently selected file; you can select the "ancestor" of the currently selected file — this is all indicated by bit masks in the argument P1. You can also select files by path from IDs. The available selection methods are determined by the manufacturer of the card or card application. As a rule, not all of them are available.

The SELECT command usually returns a byte array, its structure is specified inP2. There are four possible types of responses: empty, File Control Information (FCI), File Control Parameters (FCP), and File Management Data (FMD). The structure of FCI, FCP, and FCD is described in Section 7.4 of the ISO/IEC 7816-4 File and data control information standard. I'll explain more about these objects in the following sections and code examples.

However, SELECT can also return data in a different format than that described in ISO / IEC 7816-4.

❈ ❈ ❈

After selecting EF (about DF later), you can work with it (if the card allows you to do this): read, write, delete. The data in the file can be in one of three forms (Russian-language terminology is given according to the official translation of GOST R ISO:IEC 7816-4):

  • data units, section 11.3.1 of ISO / IEC 7816-4
  • records, section 11.4.1 of ISO / IEC 7816-4
  • data objects, section 11.5 of ISO / IEC 7816-4
Relevant EF structures:

  • Transparent structure (sometimes called binary or amorphous): there is no structure, only data units — blocks of bytes. The block size is a property of a particular EF. To work with such files, use the READ BINARY, WRITE BINARY, and UPDATE BINARY commands.
  • Record structure: Data is represented as separately identifiable records, which can be either fixed or variable in length. In addition, records can be organized in a circular structure, where new records automatically overwrite the oldest ones when the allocated space runs out. Use the READ RECORD (S), WRITE RECORD, UPDATE RECORD, APPEND RECORD, SEARCH RECORD, and ERASE RECORD (S) commands.
  • TLV structure: data is represented as information objects. TLV stands for Tag-length-value. I'll tell you more about this data in the next section. The PUT DATA and GET DATA commands are used for operation.
❈ ❈ ❈

Inside DF, data can only be present in the form of data objects, and an external application retrieves specific objects by tags it knows.

Theory: encoding of information objects

An information object is structured data encoded in an array of bytes. The ISO / IEC 7816-4 standard defines two main types of encoding: SIMPLE-TLV and BER-TLV. We have already met with the word TLV in the NDEF example. This stands for Tag-Length Value (Tag-Length-Value) and makes it quite easy to encode structures (where the tag is the key) into flat byte arrays.

SIMPLE-TLV

Let's first consider the simpler SIMPLE-TLV method. The data encoded in it consists of two or three fields.

First comes a one-byte tag field that stores the value from 0x01to 0xFE(0x00and 0xFFnot allowed), which is the tag number.

This is followed by one or three bytes that specify the length of the data block. If the first byte of them has a value from 0x00to 0xFEinclusive, then this is the length of the data block. If the first byte has a value0xFF, then the length is determined by the next two bytes, which specify a number in big endian format from 0 to 65535.

And the actual field with byte data of the specified length. Here are examples of data encoded in this way (I put extra spaces for readability)::

  • 84 07 A0 00 00 02 77 10 10 - tag number:0x84, length: 7 bytes, data: A0 00 00 02 77 10 10
  • 21 00 — there is only a tag with a number 0x21, the length of the data block is zero
This type of encoding does not define the structure of the data block; for it, it is simply a set of bytes of unknown structure. In other words, the parser will not be able to automatically understand that the data inside the value is also somehow encoded.

BER-TLV

The BER-TLV format is defined in the ASN standard.1 (ISO/IEC 8825-1) and ISO/IEC 7816 uses a subset of it. BER stands for Basic Encoding Rules and works on the same principle as SIMPLE-TLV, only it allows you to use longer tags and data blocks. In other words, an information object consists of tag bytes, data length bytes, and data bytes themselves.

ASN. 1 (Abstract Syntax Notation One) is a language for describing structured data that can then be serialized to a byte array and deserialized back. ASN.1 is widely used in network programming and cryptography. BER-TLV is one of the encoding methods described by the ASN language.1 structures in a byte array.

Next, I will describe the BER-TLV method as used in ISO / IEC 7816.

❈ ❈ ❈

The encoded data consists of three byte blocks: Tag, Length, and Value, which follow each other in a row.

The tag block size can be 1, 2, or 3 bytes. In SIMPLE-TLV, this was only a single byte and it was also an identifier (number) of the data type. However, in BER-TLV, tag bytes have a specific bit structure to adequately convey descriptions from ASN. 1.

The two most significant bits b8 and b7 of the first byte define the tag class:

  • 0 0 denotes a generic class that is defined in the ASN specification.1. the universal class includes, for example, integer values, strings, and so on
  • 0 1 denotes the application class, these tags are defined in the ISO/IEC 7816 standard
  • 1 0 denotes a context-sensitive class, these tags are defined in the standard in ISO / IEC 7816
  • 1 1 denotes a private class. There are no tags for this class in ISO/IEC 7816
The concept of a tag class goes deep from the ASN.1 specification.
A universal class tag means that the data field must match the specified universal type (i.e. tag number) from the ASN.1 specification.
But all other classes can be arbitrary and the standard does not impose any restrictions on their use. However, the following data type conventions are accepted for tags of different classes.
The application class tag defines a data type that is specific to a given domain.It is unique within the domain, sets data of a specific nature, and is not used for anything else. In our case, application class tags are defined in ISO / IEC 7816 and should be used in the same form by other standards.
Private class tags are usually not used at all.
The context-specific tag defines the data type whose semantics are determined by the context. This class is used most often.
For more information about classes, see Chapter 4 Tagging in ASN.1 Complete (page 172).

Bit b6 of the first byte defines the structure of the data block:
  • 1 means that the data block is not just a byte array, but is encoded using BER-TLV (constructed data).;
  • 0 means that a block of data is simply an array of bytes of unknown structure, primitive encoding, and primitive data.

The first byte bits b5 through b1 are used as follows:
  • If there is at least one 0 among the bits from b5 to b1, then the resulting number is the tag number and the tag ends there. In this case, the tag number ranges from 00000 to 11110 (in decimal from 0 to 30, in hex from 0x00to0x1E).
  • 1 1 1 1 1 means that the tag continues in the next or several bytes and does not participate in tag number calculations. In each subsequent byte, the value of bit b8=1 means that this byte is included in the tag. The value b8=0 means that this byte is the last one in the tag. Bits b7 through b1 are concatenated over all participating bytes and form the tag number.
ISO / IEC 7816 allows a maximum of three bytes for a tag, with a larger number reserved for future reference. Three bytes can encode tag numbers from 0 to 16383 (hex 00-3F FF).

This is a very important point: unlike SIMPLE-TLV, where only the tag number is written in tag bytes, in BER-TLV, this field contains the data encoding type, tag number, and tag class; this triple defines the data type. But for practical use, you don't need to know all this, you just need to correctly parse the binary data, select the tag, and then look in the standards for what data should lie in the data block of this tag.
Here are two examples: 0x6Fthe and tags 0x9F38.

BER-TLV tags example


❈ ❈ ❈

Then there are bytes that determine the length of the data block.

If bit b8 of the first byte is 0, then subsequent bits specify the length of the data block. In this case, you can encode a number from 0 to 127.

If bit b8 of the first byte is 1, then subsequent bits specify the number of subsequent bytes that determine the length of the data block.

In ISO / IEC 7816-4, a maximum of five bytes can be used to encode the length, including the first byte.

❈ ❈ ❈

And the actual data. If the tag specifies that the data is primitive, then this byte array is passed as-is and must be interpreted in some way by the program.

If the data is constructed, then this block contains several objects encoded in BER-TLV in a row (I will continue to call them fields, this is an informal and non-standard designation, just so convenient). You can think of such a block as an array of nested BER-TLV objects, or a tree. However, keep in mind that a block can contain several objects with the same tags.In other words, you can't treat them as a structure with tag fields. This is an ordered list.

❈ ❈ ❈

A set of BER-TLV information objects that form an integral semantic unit is called a template. ISO / IEC 7816 defines several cross-industry templates, and other specifications additionally define their own. The operation description may indicate that it returns, say, an FCI Template; this means that somewhere nearby there should be a description of this template, what BER-TLV objects it consists of, and how to interpret their values.

Each template has its own application class tag reserved for it. If you encounter a BER-TLV object with this tag, you can safely assume that the object's structure matches the description of the appropriate template.

❈ ❈ ❈

Here are some examples of data encoding via BER-TLV.

50 07 49 4E 54 45 52 41 43
Tag: 50 (01010000, application class, tag number: 0x10, primitive data encoding)
Length: 07
Data: 49 4E 54 45 52 41 43 (ASCII value: "INTERAC")


BF 3D 05 86 02 01 00
Tag: BF3D (10111111 00111101, context-specific, tag number: 0x3D, BER-TLV data)
Length: 04
Data: 86 02 01 00
Tag: 86 (100001100, context-specific, tag number 0x6, primitive data encoding)
Length: 02
Data: 01 00
❈ ❈ ❈

Bytes encoded in BER-TLV can be fully parsed into a recursive key-value structure, where the key is the binary value of the tag, and the value is either an array of bytes, if the tag defines primitive data, or a list of the same structures, if the tag defines constructed data.

BER-TLV is described in detail in section 6.3 of BER-TLV data objects of the ISO/IEC 7816-4 standard, as well as in section 8.1.2 of the ISO/IEC 8825-1 ASN.1 standard (also known as X. 680). You can download the current version from the official website: https://www.itu.int/ITU-T/recommendations/rec.aspx?rec=14468.

example-12: Reading ISO / IEC 7816-4 data using a bank card example.

Source code of the program: example-12/emv-card-read

For this example, you can use both types of readers: contactless and contact chip cards. You will also need any bank card with a chip.

The goals of this example are:
  • show how data is encoded and decoded in BER-TLV;
  • demonstrate how to work with files on the card according to the ISO/IEC 7816-4 standard;
  • show you how to work with the EMV standard.
Just a note. If you try to execute instructions from this example on a memory card (for example, SLE4442 or Mifare Classic), you will get an error at the library level, because the card simply does not understand such commands.

Processing the result of executing a command in the T=0 protocol.

Earlier in the section Data Exchange protocols, I mentioned that there are two protocols for data transfer: T=0and T=1. When working with contact cards, it is often supported only T=0and when using it, there is a special feature of transmitting response data: they can be returned not immediately after the command, but as a result of consecutive calls to a separate GET RESPONSE command.

To handle this situation, I made a separate function transmit_wrapperthat collects the result for several requests and returns it as if it was returned all at once.

def transmit_wrapper(connection, apdu):
response, sw1, sw2 = connection.transmit(apdu)
if sw1 == 0x61:
response_data = []
ne = sw2
while True:
gr_apdu = '00 C0 00 00 {:02X}'.format(ne)
response, sw1, sw2 = connection.transmit(toBytes(gr_apdu))
if (sw1,sw2) == (0x90,0x00):
response_data.extend(response)
break
elif sw1 == 0x61:
response_data.extend(response)
ne = sw2
continue
else:
# error, pass sw1, sw2 back to caller
response_data = []
break

return response_data, 0x90, 0x00
else:
return response, sw1, sw2
The value 61 XXin the status word means that there is still data that the command can return. In this case, the XXamount that is available is encoded in. To get data, we call the GET RESPONSE command with the code C0and specify the value in the Le field XX. This command can also return a status word 61 XX, so we call GET RESPONSE until we get all the data (which the status word will signal 90 00).

If you are interested in exactly how data transmission is organized in the T=0 protocol, I recommend reading this article on habré.

General principles of working with a bank card

Bank cards comply with the EMV standard, and its full specification is available at https://www.emvco.com/emv-technologies/contact/. All documents are divided into several books covering different aspects of the standard.

The process of using a bank terminal (IFD, it can be an ATM or POS terminal in a store) with a bank card (ICC) consists of several stages:

  1. The ICC is inserted into the IFD, the IFD recognizes the connection and activates the electrical contacts.
  2. The RESET operation for the ICC is performed, IFD receives the card parameters from the card and establishes a connection.
  3. A transaction (or multiple transactions) is being executed.
  4. The card is disconnected from the contacts and removed from the terminal reader.
Since we work inside the PC/SC infrastructure, points 1 and 2 occur inside the reader without our participation, approximately the same with point 4.Point 3 remains.

Point 3 is divided into the following stages:
  1. Application Selection-described in EMV_v4. 3, Book 1, section 12 Application Selection.
  2. Executing transaction Commands-described in EMV_v4. 3, Book 3, section 10 Functions Used in Transaction Processing.

Bank card: Selecting the app

The files of each payment system (for example, Visa or Mastercard) must be located inside its own DF, called the Application Definition File (ADF). These ADFS must have fixed names (ADF Name), which are assigned centrally by a special organization. I already wrote above that such names are called Application Identifier (AID) and they are predefined for Visa, Mastercard, or any other system. As a rule, the card has only one ADF for one payment system.

IFD must find the payment system application on the card and select it for further work. There are two ways to do this.

First, the card may contain a special DF with a name1PAY.SYS.DDF01, called PSE (Payment System Environment) by EMV. This" directory " stores a list of identifiers of payment systems recorded on the ADF card. The presence of a PSE is not guaranteed. In addition, it can be an MF, but it is not required.

Secondly, if the program doesn't find the PSE, it tries to sort through all the payment system aids it knows. This procedure for using PSE is described in section 12.3.2 Using the PSE of Book 1 of EMV_v4. 3.

For contactless cards, a DF with a special name is used 2PAY.SYS.DDF01. It must always be present.

ADF search via PSE

C-APDU for selecting a named DF 1PAY.SYS.DDF01looks like this (SELECT command, ISO / IEC 7816-4, section 11.2.2 SELECT command):

CLA INS P1 P2 Lc Data
00 A4 04 00 0E 31 50 41 59 2E 53 59 53 2E 44 44 46 30 31
Here P1=04it means that in the DATA field we pass the name DF (DF name), and for the application, the ADF name is its identifier. We pass the name as a byte string encoded in ASCII.

P2=00 This means (see Table 63 in ISO / IEC 7816-4) that we want to get an answer in the form of an FCI template for the first or only result.

Previously, any result in the status word (SW) was not 0x9000considered an error for us, but this time we must also process the status word 6A 82, which in the context of EMV means the PSE file was not found. In this case, we will first try to find the PSE using the name 2PAY.SYS.DDF01(for contactless cards), and if this fails, we will search for the desired application by searching through previously known IDs. In case of other errors, we assume that we do not support this card.

# Select PSE first
# CLA INS P1 P2 Lc DATA
apdu = '00 A4 04 00 0E 31 50 41 59 2E 53 59 53 2E 44 44 46 30 31'
response, sw1, sw2 = transmit_wrapper(cardservice.connection, toBytes(apdu))
if (sw1,sw2) == (0x90,0x00):
# take AID from FCI
pse_data = response
aid = get_AID_from_PSEFCI(cardservice.connection, pse_data)
if aid is None:
aid = guess_AID(cardservice.connection)
elif (sw1,sw2) == (0x6A,0x82):
# try with PPSE "2PAY.SYS.DDF01" for contactless cards
apdu = '00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31'
response, sw1, sw2 = transmit_wrapper(cardservice.connection, toBytes(apdu))
if (sw1,sw2) == (0x90,0x00):
# take AID from FCI
pse_data = response
aid = get_AID_from_PSEFCI(cardservice.connection, pse_data)
if aid is None:
aid = guess_AID(cardservice.connection)
elif (sw1,sw2) == (0x6A,0x82):
# guess AID
print('guessed')
aid = guess_AID(cardservice.connection)
else:
print('Failed to read card.')
print('{:02X} {:02X}'.format(sw1,sw2))
return 1
To get the AID, we use two functions: get_AID_from_PSEFCIto extract the AID from the PSE structure and guess_AIDto guess (more precisely, iterate through known) aids.

Parsing the FCI template (get_AID_from_PSEFCI).

The result of executing the SELECT statement is data that corresponds to the FCI template. This template is described in section 7.4.1 File control information retrieval of the ISO/IEC 7816-4 standard. In my case, the reader returned an array like this (I divided it into groups of 16 bytes for convenience):

6F 28 84 0E 31 50 41 59 2E 53 59 53 2E 44 44 46
30 31 A5 16 88 01 01 5F 2D 08 72 75 65 6E 66 72
64 65 BF 0C 05 9F 4D 02 0B 0A
Let's analyze it once manually to show how it's done.

I will immediately note that the SELECT statement returns a BER-TLV object, and since we requested the FCI Template, the corresponding tag-is expected for this object 6F. The standard defines the FCI template as a set of file control parameters and file control information.

If we look at the binary representation 6F = 0110 1111, we see the following (see the section above describing BER-TLV):

  • b8=0the and bits b7=1indicate the tag class. In our case, this is the application class;
  • a bit b6=1indicates that the data is encoded in BER-TLV;
  • the bits b5..b1=01111encode the tag number, in our case it 0xFis 15 in decimal, and the block with the tag ends with this, since b5..b6there is at least one zero among the bits.
The next block is the length of the data block,0x28 = 0010 1000, bit b8=0means that the length of the data block is encoded in the remaining bits and the block with the length ends there. That is, the length of the data block is forty bytes (0x28 = 40).

We look at the next byte (the first byte of the response block)—0x84 = 1000 0100, this is a tag, a context-sensitive class, the data is not encoded, the tag number is 4. And the next byte is the length—0x0E, that is, 14 bytes — 31 50 41 59 2E 53 59 53 2E 44 44 46 30 31.

But the data didn't end there, and we need to continue the analysis further. The new block starts with a byte0xA5 = 1010 0101, this is a tag, a context-sensitive class, the data is encoded in BER-TLV, the tag number is 5. The next block is the data length—0x16, this is 22 bytes, just before the end of all data.

ISO / IEC 7816-1 does not define a general way in which DF refers to files "owned" by it, and this process is left to the discretion of third-party specifications. Usually, DF refers to an EF that lists other EFS or DFS, such a file is commonly referred to as DIR. In the case of EMV, application files are defined via an object with a proprietary tag A5.
For the purposes of this example, I wrote my own fairly simple BER-TLV parser (see bertlv.py). This module implements:

  • a class Tlvthat encapsulates a single TLV object. the field tagstores the tag itself as an integer (the tag bytes represent its big-endian form), and the field valuecontains a byte array if the TLV object is primitive (primitive), and a list of nested objects of the Tlv class if the TLV object is composite (constructed). I will also refer to nested TLV objects as fields or parts;
  • a function parse_bytesthat takes a byte list as input and returns a list of TLV objects encoded in it;
  • a function find_tagthat takes an integer tag and a list of class objects as inputTlv, and returns the first object found with the specified tag, or None if nothing is found.
❈ ❈ ❈

Let's start with the functionget_AID_from_PSEFCI, its entire code in the program text example-12 / emv-card-read, and here only important fragments with comments.

The input is a byte array with FCI, its structure is described in ISO / IEC 7816-4, section 7.4.1 File control information retrieval. This is a BER-TLV object with a tag 6F, it contains other objects with different tags, but the information we need is stored in a block with a tag A5. This is a special tag for proprietary data, the structure of which is determined by some other standard (not ISO/IEC 7816). In our case, this is the standard EMV_v4. 3, Book 1, section 11.3.4 Data Field Returned in the Response Message. Here's how we request a nested object with a tagA5:

parts = bertlv.parse_bytes(data)
t = bertlv.find_tag(0x6F, parts)
if t is None:
# expecting FCI template
return None
# pi means "proprietary information"
piTlv = bertlv.find_tag(0xA5, t.value)
if piTlv is None:
# Cannot find EMV block in PSE FCI
return None
In an object piTlvof all possible fields, we are interested in a field with a tag 88. This is a context-sensitive class taken from ISO / IEC 7816-4. The tag marks Short EF identifier (SFI) and this object contains a short EF identifier, which is a number in the range from 1 to 30 inclusive, identifying a specific file within the card boundaries. In SFI, you can perform separate operations on this file without having to first select the file using the SELECT statement. In our situation, this file contains the SFI Payment System Directory (PSD), i.e. a directory of all payment systems on the card.

This is how we read the SFI value:

sfiTlv = bertlv.find_tag(0x88, piTlv.value)
if sfiTlv is None:
# Cannot find SFI block in PSE FCI
return None
defSfiData = sfiTlv.value
sfi = defSfiData[0]
Now we can read the PSD. This is a linear record structure (ISO / IEC 7816-4, section 7.3.4 Data referencing methods in elementary files). We must read all records from the file, this is done by the READ RECORD instruction from ISO / IEC 7816-4, section 11.4.3 READ RECORD (S) command. The base APDU of this command is set in the code as follows:

p2 = (sfi << 3) | 4
# CLA INS P1 P2 Le
apdu = '00 B2 00 {:02X} 00'.format(p2)
apduTpl = toBytes(apdu)
We must pass the following data in the corresponding bit fragments in the P2 argument (details are described in ISO / IEC 7816-4, Tables 71 and 72)::

  • file by its SFI, not the currently selected file (write SFI to the high five bits of P2);
  • read the record by number from P1 (write the bits 1 0 0to the lower three bits of P2).
This is done in code p2 = (sfi << 3) | 4.

Now we will sequentially execute this instruction with an increasing record number, starting from 1. There is a nuance here: in APDU, we specified a byteLe=0, which means to return all data at once, but in some cards, the command can return an error 6CXXin the status word, which means that you need to repeat the request, only instead Le=0of specifying the exact lengthLe=XX, here XXit is the second byte from the status word (i.e. SW2).

The structure of each record is defined in EMV_v4. 3, book 1, section 12.2.3 Coding of a Payment System Directory: an object with a tag70, inside several objects with a tag61, inside which the tag field 4Fcontains the name of the payment system application (Application Definition File name, ADF name), and we need it.

foundAIDs = []
recordNumber = 1
expectedLength = 0
while True:
apdu = apduTpl
apdu[2] = recordNumber # P1
apdu[4] = expectedLength # Le
response, sw1, sw2 = transmit_wrapper(connection, apdu)
if sw1 == 0x6C:
expectedLength = sw2
continue
if (sw1,sw2) != (0x90,0x00):
break
if len(response) > 0:
parts = bertlv.parse_bytes(response)
psd = bertlv.find_tag(0x70, parts)
# psd must have tag 0x70
# see EMV_v4.3 book 1, section "12.2.3 Coding of a Payment System Directory"
if psd is None:
return None
for t in psd.value:
if t.tag == 0x61:
aidTlv = bertlv.find_tag(0x4F, t.value)
if aidTlv is not None:
foundAIDs.append(aidTlv.value)
recordNumber += 1
expectedLength = 0
Here in this fragment, we again jump to the beginning of the loop if SW1 contains 0x6C, but first we specify the resulting expectedLength value from SW2, and at the end of the loop, do not forget to reset expectedLength again:

if sw1 == 0x6C:
expectedLength = sw2
continue
We save the collected AID IDs to the foundAIDs list. At the end, we simply take the first element from it and return it, and if it is empty, we return None.

The template parsing approach described here absolutely cannot be used in real industrial code. The essence of BER-TLV is to parse the data encoded in it automatically in accordance with the formal description in the ASN language.1. And manual parsing with a bunch of checks and "descending" down the hierarchy from the root BER-TLV object is unnecessarily verbose and can potentially lead to serious errors. Therefore, treat this code only as a training code.
Ideally, you should have ASN. 1-specifications of all templates and code that receives a byte array and template specification at the input, and at the output either returns a parsed object with fully parsed data, or throws an exception with an error description.

ADF search without PSE

Now let's consider the situation when the card does not have a PSE (Payment System Environment). In this case, EMV_v4. 3 prescribes (Book 1, section 12.3.3 Using a List of AIDs) to iterate through DFS based on known payment system identifiers.

The code for this function is trivial: we just go through the list of predefined aids and try to select a DF with that name.

def guess_AID(connection):
print('guess')
candidateAIDs = [
'A0 00 00 00 03 20 10', # Visa Electron
'A0 00 00 00 03 10 10', # Visa Classic
'A0 00 00 00 04 10 10', # Mastercard
'A0 00 00 06 58 10 10', # MIR Credit
'A0 00 00 06 58 20 10' # MIR Debit
]
foundAID = None
# CLA INS P1 P2 Le
apduTpl = '00 A4 04 00 07'
for aid in candidateAIDs:
apdu = apduTpl + aid
response, sw1, sw2 = transmit_wrapper(connection, toBytes(apdu))
if (sw1,sw2) == (0x90,0x00):
foundAID = toBytes(aid)
break

return foundAID

ADF selection and FCI analysis

Now that the payment app's AID is known, you can start working with it. Since we are not writing a real bank terminal, we will limit ourselves to just pulling out free-lying data.

Selecting the application's DF is done in the same way as selecting the PSE DF — via the SELECT command. Let's continue with the AID obtained at the last stage in the variableaid:

apdu = '00 A4 04 00 07 ' + toHexString(aid)
response, sw1, sw2 = transmit_wrapper(cardservice.connection, toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('No EMV app found.')
return 1

parts = bertlv.parse_bytes(response)
fciTlv = bertlv.find_tag(0x6F, parts)
The result of these commands is an FCI Template. For my card, this set of bytes looks like this:

6F 2C 84 07 A0 00 00 00 03 10 10 A5 21 50 04 56
49 53 41 5F 2D 04 72 75 65 6E 87 01 01 9F 11 01
01 9F 12 0A 56 69 73 61 20 44 65 62 69 74
After decoding, we get the following TLV structure:

6F 8407A0000000031010A5215004564953415F2D047275656E8701019F1101019F120A56697361204465626974
+- 84 A0000000031010 // Dedicated File (DF) Name
+- A5 5004564953415F2D047275656E8701019F1101019F120A56697361204465626974 // FCI Proprietary Template
+- 50 56495341 // Application Label, "VISA"
+- 5F2D 7275656E // Language Preference, "ruen"
+- 87 01 // Application Priority Indicator
+- 9F11 01 // Issuer Code Table Index
+- 9F12 56697361204465626974 // Application Preferred Name, "Visa Debit"
This template is fully described in the EMV_v4.3 standard, Book 1, section 11.3.4 Data Field Returned in the Response Message, Table 45: SELECT Response Message Data Field (FCI) of an ADF.

The tag element A5contains EMV-specific information. Here are the elements from our example ((M) means mandatory, required field; (O) — optional, optional):

  • tag0x50-Application name — M) - stores the name of the payment system, in this case VISA;
  • tag0x5F2D-Language Preference — O) - the preferred language is stored here as a maximum of four two-letter codes from ISO 639, in this case 72 75 65 6Eit is a string"ruen";
  • tag0x87-Application Priority Indicator (O) - an indicator of the priority of the application relative to other applications on the same card, described in the table in the same place Table 48: Format of Application Priority Indicator;
  • tag0x9F11-Issuer Code Table Index (O) - code table of characters for displaying Application Preferred Name (according to ISO/IEC 8859). In our case, the value 0x01means ISO/IEC 8859-1, i.e. latin-1 encoding
  • the tag 0x9F12is Application Preferred Name (O). It stores the name of the application that the terminal can use to display in addition to the Application name. We have a string here "Visa Debit"
Additionally, other fields may be included, such as:

  • tag0xBF0C-FCI Issuer Discretionary Data (O) — this field includes all other information from the card manufacturer, bank, application manufacturer, card operating system, and so on. Valid tags and their semantics are defined in EMV_v4.3, Book 3, section Annex B Rules for BER-TLV Data Objects.
  • The tag0x9F38-PDOL (O) - Processing Options Data Object List, contains special data required to start a financial transaction. This field is very important. In it, the application specifies what additional data it needs to initialize a financial transaction. I'll show you how to use this value below.
Other FCI Template Examples:

6F 48 84 07 A0 00 00 00 04 10 10 A5 3D 50 0A 4D
61 73 74 65 72 43 61 72 64 87 01 01 9F 11 01 01
9F 12 0A 4D 61 73 74 65 72 43 61 72 64 5F 2D 08
72 75 65 6E 66 72 64 65 BF 0C 0F 9F 4D 02 0B 0A
9F 6E 07 06 43 00 00 30 30 00
6F 8407A0000.......000
+- 84 A0000000041010 // Mastercard
+- A5 500A4D6173746572436172648701019F1101019........0B0A9F6E0706430000303000
+- 50 4D617374657243617264 // "MasterCard"
+- 87 01 // Application Priority Indicator
+- 9F11 01 // Issuer Code Table Index
+- 9F12 4D617374657243617264 // Application Preferred Name, "MasterCard"
+- 5F2D 7275656E66726465 // Language Preference, "ruenfrde"
+- BF0C 9F4D020B0A9F6E0706430000303000 // FCI Issuer Discretionary Data
+- 9F4D 0B0A // Log Entry
+- 9F6E 06430000303000 // Third Party Data / Form Factor Indicator
And more:

6F 1F 84 07 A0 00 00 00 03 10 10 A5 14 50 0C 56
69 73 61 20 43 6C 61 73 73 69 63 9F 38 03 9F 1A
02
6F 8407A0000000031010A514500C5669736120436C61737369639F38039F1A02
+- 84 A0000000031010 // Visa International - VISA Debit/Credit (Classic)
+- A5 500C5669736120436C61737369639F38039F1A02
+- 50 5669736120436C6173736963 // Application Label, "Visa Classic"
+- 9F38 9F1A02 // Processing Options Data Object List (PDOL)
And another example with the WORLD card:

6F 34 84 07 A0 00 00 06 58 10 10 A5 29 87 01 01
9F 38 0F 9F 7A 01 5F 2A 02 9F 02 06 9F 35 01 9F
40 05 5F 2D 04 72 75 65 6E BF 0C 05 9F 4D 02 18
0A 50 03 4D 49 52
6F 8407A0000006581010A5298701019F380F9F7A015F2A029F02069F35019F40055F2D047275656EBF0C059F4D02180A50034D4952
+- 84 A0000006581010 // MIR Credit
+- A5 8701019F380F9F7A015F2A029F02069F35019F40055F2D047275656EBF0C059F4D02180A50034D4952
+- 87 01 Application Priority Indicator
+- 9F38 9F7A015F2A029F02069F35019F4005 // Processing Options Data Object List (PDOL)
+- 5F2D 7275656E // Language Preference, "ruen"
+- BF0C 9F4D02180A // File Control Information (FCI) Issuer Discretionary Data
+- 9F4D 180A // Log Entry
+- 50 4D4952 // Application Label, "MIR"
❈ ❈ ❈

We will read and parse the contents of the tag element 0xA5. Here we will simply go through all the nested objects and print out each one that we know:

piTlv = bertlv.find_tag(0xA5, fciTlv.value)

# read all fields from FCI Proprietary Template
pdolData = None
for t in piTlv.value:
if t.tag == 0x50:
print('Application name: {}'.format(HexListToBinString(t.value)))
elif t.tag == 0x87:
print('Application Priority Indicator: priority={}, confirmation required={}'
.format(t.value[0] & 0x0F, (t.value[0] >> 7 == 1)))
elif t.tag == 0x9F38:
print('PDOL is present')
pdolData = t.value
elif t.tag == 0x5F2D:
print('Language preference: {}'.format(HexListToBinString(t.value)))
elif t.tag == 0x9F11:
print('Issuer Code Table Index: ISO 8859-{}'.format(t.value[0]))
elif t.tag == 0x9F12:
print('Application Preferred Name: {}'.format(HexListToBinString(t.value)))
elif t.tag == 0xBF0C:
print('FCI Issuer Discretionary Data is present')
else:
print('Unknown tag {:X}: '.format(t.tag, toHexString(t.value)))

Start of a financial transaction via GET PROCESSING OPTIONS

Once the payment application is selected, a Financial Transaction begins — which is a separate set of commands and algorithms described in Book 3 of EMV_v4.3. The term itself is defined as follows: Financial Transaction — The act between a cardholder and a merchant or acquirer that results in the exchange of goods or services against payment.

The first command in a financial transaction is GET PROCESSING OPTIONS, which is described in Section 6.5.8 GET PROCESSING OPTIONS Command-Response APDUs of Book 3 EMV_v4.3. The C-APDU of this command looks like this:

CLA INS P1 P2 Lc Data Le
80 A8 00 00 [Lc] XX XX .. XX 0
Please note right away that the command class here is proprietary — 80. The arguments P1and P2must be set to 0.The Data field is passed a specially formed byte array DOL (Data Object List) in the form of a BER-TLV object with tag 83.

The full scheme for constructing this array is described in section 5.4 Rules for Using a Data Object List (DOL) of Book 3 EMV_v4. 3, and I will tell you briefly. The point of DOL is to pass some parameters to the card that it needs to start a financial transaction. The names (more precisely, tag values) of the required parameters are encoded in a special way in the PDOL field from the FCI obtained when selecting ADF.

The structure PDOLis similar to BER-TLV: tag, length, but there is no data block. The next tag starts immediately instead. You can imagine it like this:

{T1} {L1} {T2} {L2} {T3} {L3} ... {Tn} {Ln}
The size of each {Ti}one is one or two bytes (they are formed according to the same rules as tags in BER-TLV), the size {Li}is one byte. PDOL determines what additional information the card expects from us in order to correctly execute the command. For example, the tag 0x9F35specifies the Terminal Type, and the tag 0x9F1Aspecifies the two-character country code. The types and values of specific parameters depend on the payment system and there is no single register of them.

To make it clearer, here is an example of PDOL from the WORLD card:

9F 7A 01 5F 2A 02 9F 02 06 9F 35 01 9F 40 05
Breaking it down into components:

{T1} {L1} {T2} {L2} {T3} {L3} {T4} {L4} {T5} {L5}
{9F7A} {01} {5F2A} {02} {9F02} {06} {9F35} {01} {9F40} {05}
The DOL is formed only from data bytes (without tags) that run in a row in the same order as the elements in the PDOL. The data is preceded by a tag 83and a byte with a total length equal {L1} + {L2} + ... + {Ln}to . For example, for the example above, tags have the following values:

TagLengthDescription
0x9F7A1VLP Terminal Support Indicator, determines whether the terminal supports VLP (Visa Low-Value Payment) functionality. This is the ability to conduct very fast transactions for small amounts.
0x5F2A2Transaction Currency Code, sets the transaction currency code according to ISO 4217
0x9F026Amount, Authorized (Numeric), sets the authorized transaction size, and is encoded according to the rules defined in Section 4.3 of Book 3 of EMV_v4. 3 (type n)
0x9F351Terminal Type, specifies the type of terminal, its capabilities, and is encoded in accordance with Appendix A1 of Book 4 EMV_v4. 3
0x9F405Additional Terminal Capabilities, encoded in accordance with Appendix A3 of Book 4 EMV_v4. 3
The terminal program can use this list to generate and transmit a value from 15 (1+2+6+1+5) bytes, specifying the desired values in each of the fragments. If the program doesn't know the PDOL tag or doesn't want to fill it in, it can pass a zero-filled fragment of the appropriate length. This is exactly what we'll do for simplicity's sake.

If there was no PDOL field in the FCI ADF, then we pass Lc=02and83 00, i.e. C-APDU: 80 A8 00 00 02 83 00 00. You can't pass 83 00it if PDOL was specified.

Prepare a DOL (create a zero-filled array of the desired length in the dolData variable):

dolData = [0x83, 0x00]
if pdolData is not None:
# parse PDOL data
lengthByte = False
totalLength = 0
for b in pdolData:
if lengthByte:
totalLength += b
lengthByte = False
continue
if b & 0x1F != 0x1F:
# ^^^^^^ last five bits of "b" are not all 1s, so this byte is last one
# in tag block, so consider next byte as field length
lengthByte = True
dolData[1] = totalLength
dolData.extend([0] * totalLength)
And call the GET PROCESSING OPTIONS statement:

# Send command "GET PROCESSING OPTIONS"
# CLA INS P1 P2 Lc DATA
apdu = '80 A8 00 00 {:02X} {}'.format(len(dolData), toHexString(dolData))
response, sw1, sw2 = transmit_wrapper(cardservice.connection, toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('GET PROCESSING OPTIONS failed')
return 1
❈ ❈ ❈

The result of executing the GET PROCESSING OPTIONS command is a BER-TLV object with two values encoded: AIP and AFL. If the tag is this object0x77, then they are represented as nested BER-TLV objects. If a tag 0x80, then as a byte string.

  • AIP-Application Interchange Profile-determines which functions the payment application supports on the card, the size of this data block is 2 bytes.
  • AFL-Application File Locator — a list of files and records that the external application should read.
Here is one example of the result of the GET PROCESSING OPTIONS command.

80 0E 7C 00 08 01 01 00 10 01 05 00 18 01 02 01
The tag 0x80tells us that this is primitive data, so its structure is as follows:

TagLengthValue
AIPAFL
800E7C 0008 01 01 00 10 01 05 00 18 01 02 01
Here is another example of the result, here the same data is already encoded in BER-TLV (tag 77):

77 12 82 02 7C 00 94 0C 08 01 01 00 10 01 05 00 18 01 02 01
In the decoded form, they look like this:

TAG: 77(CONSTRUCTED)
TAG: 82(PRIMITIVE)
VALUE: 02 7C
TAG: 94(PRIMITIVE)
VALUE: 0C 08 01 01 00 10 01 05 00 18 01 02 01
Here, the field with the tag 0x82stores AIP, and the field with the tag 0x94stores AFL.

And here is the code for processing the result of the command:

aipData = None
aflData = None
parts = bertlv.parse_bytes(response)
t = parts[0]
if t.tag == 0x77:
x = bertlv.find_tag(0x82, t.value)
if x is not None:
aipData = x.value
x = bertlv.find_tag(0x94, t.value)
if x is not None:
aflData = x.value
elif t.tag == 0x80:
aipData = t.value[0:2]
aflData = t.value[2:]
In AIP, data is encoded in bits, and its structure is described in Table 37 of Book 3 of EMV_v4.3. In this code snippet, I display the information available in this data block:

print('Application Interchange Profile')
print(' SDA supported: {}'.format('no' if (aipData[0] & 0x40)==0 else 'yes'))
print(' DDA supported: {}'.format('no' if (aipData[0] & 0x20)==0 else 'yes'))
print(' Cardholder verification is supported: {}'.format('no' if (aipData[0] & 0x10)==0 else 'yes'))
print(' Terminal risk management is to be performed: {}'.format('no' if (aipData[0] & 0x8)==0 else 'yes'))
print(' Issuer authentication is supported: {}'.format('no' if (aipData[0] & 0x4)==0 else 'yes'))
print(' CDA supported: {}'.format('no' if (aipData[0] & 0x1)==0 else 'yes'))

Reading payment app data

Now we are ready to read the application data, it is stored in files that are referenced by data from AFL.

The AFL structure is described in Section 10.2 of Book 3 EMV_v4.3. This is a list of consecutive fours of bytes, within each four bytes have the following semantics:

  • the five most significant bits (i.e. b8, b7, b6, b5, b4) of the first byte define the Short File Identifier (SFI), the remaining three bits must be zero;
  • the second byte specifies the number of the first record to be read from the file defined by SFI;
  • the third byte specifies the number of the last record to be read (inclusive); it is greater than or equal to the second byte;
  • the fourth byte defines the number of records involved in offline data authentication (this topic is described in Book 2 EMV_v4. 3); these records start with the one specified in the second byte; this byte can be zero.
The terminal software must read all specified files and records in the order in which they are listed in the AFL. Records are read with the READ RECORD command from ISO / IEC 7816-4. Each record is a BER-TLV object, and field tags are defined in EMV_v4.3. The client program must save the data from these records in order to use it in the next steps.

Consider, for example , the AFL value28 01 03 01 30 01 02 00. It consists of two fours: 28 01 03 01and 30 01 02 00. Let's consider the first of them in detail:

┌─────────── 28 = 0 0 1 0 1 0 0 0 → SFI = 0 0 1 0 1 = 5
│ ┌──────── номер первой записи для SFI
│ │ ┌───── номер последней записи для SFI
│ │ │ ┌── количество записей, необходимых для аутентификации карты
28 01 03 01
This block describes three entries in the c file SFI=05. To read these entries, you need to execute three commands with the following C-APDU (note that CLA=00, since this is an inter-branch instruction from ISO / IEC 7816-4):

CLA INS P1 P2 Le
────────────────
00 B2 01 2C 00
00 B2 02 2C 00
00 B2 03 2C 00
B P1passes the record number, in our case from 1 to 3, P2and c describes from which file. In a bit record, P2SFI is written in the first five bits (b8 to b4), and in the last three -1 0 0, that is, read only the record specified in P1. Similarly, the second four is analyzed.

❈ ❈ ❈

Let's break the AFL into components, parse them, read all entries from the specified files, and get a flat list of BER-TLV objects in the variablereadObjects:

readObjects = []
aflPartsCount = len(aflData) // 4
for i in range(aflPartsCount):
startByte = i * 4
sfi = aflData[startByte] >> 3
firstSfiRec = aflData[startByte + 1]
lastSfiRec = aflData[startByte + 2]
offlineAuthRecNumber = aflData[startByte + 3] # we don't use this value

# CLA INS P1 P2 Le
apdu = toBytes('00 B2 00 00 00')
for j in range(firstSfiRec, lastSfiRec + 1):
# set Le=0
apdu[4] = 0
# set P1, record number to read
apdu[2] = j
# set P2, coding of this parameters defined in ISO/IEC 7816-4, section "READ RECORD (S) command"
p2 = (sfi << 3) | 4
apdu[3] = p2
response, sw1, sw2 = transmit_wrapper(cardservice.connection, apdu)
if sw1 == 0x6C:
# set new Le and repeat command
apdu[4] = sw2
response, sw1, sw2 = transmit_wrapper(cardservice.connection, apdu)
if (sw1,sw2) != (0x90,0x00):
print('Failed to read record {} in SFI {}'.format(j, sfi))
continue
parts = bertlv.parse_bytes(response)
rectplTlv = bertlv.find_tag(0x70, parts)
if rectplTlv is None:
print('Failed to parse record {} in SFI {}'.format(j, sfi))
continue
for t in rectplTlv.value:
readObjects.append(t)
Each record (defined by the SFI and record number) contains a TLV object with a tag 0x70. In fact, this is just a container for storing other TLV objects. We read them and save them to a variable readObjectsfor processing later. B2We execute the request with the instruction twice: first to get the size of the return value (specify inLe=0), and then with the specific value Lespecified, as we already did in the get_AID_from_PSEFCI function.

❈ ❈ ❈

Now let's print out the contents of all read objects in a readable form. To do this, we need to know the name for each of the tags and the format of the data stored in it. For this purpose, I made a simple emvutil module.py with two functions:

  • emv_object_name(tag) — human-readable name of the element identified by the tag tag
  • emv_object_repr(tag, value) — human-readable data representation of the element identified by the tag tag
We sequentially go through all the stored elements and print their contents:

print('EMV objects:')
for t in readObjects:
print(' {}: {}'.format( emvutil.emv_object_name(t.tag), emvutil.emv_object_repr(t.tag, t.value) ))
emvutil.pyThere are not very many elements in the module, not even all of them from the EMV specifications (Book 3 EMV_v4.3). If an element is not defined there, then its representation as numbers in HEX notation is output instead of the name.

❈ ❈ ❈

Here's what the program's output looks like (I've reduced some of the data and hidden some of it)::

% ./emv-card-read
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected.
Application name: MasterCard
Application Priority Indicator: priority=1, confirmation required=False
Issuer Code Table Index: ISO 8859-1
Application Preferred Name: MasterCard
Language preference: ruenfrde
FCI Issuer Discretionary Data is present
Application Interchange Profile
SDA supported: no
DDA supported: yes
Cardholder verification is supported: yes
Terminal risk management is to be performed: yes
Issuer authentication is supported: no
CDA supported: no
EMV objects:
Cardholder Verification Method (CVM) List: hex(00 00 00 00 00 00 00 00 42 01 1E 03 42 03 44 03 41 03 1F 03)
Application Effective Date: 2011-09-01
Application Expiration Date: 2021-02-28
Application Usage Control: hex(FF 00)
Application Primary Account Number (PAN): ???????????????? (hex(?? ?? ?? ?? ?? ?? ?? ??))
Application Primary Account Number (PAN) Sequence Number: 2 (hex(02))
Issuer Action Code - Default: hex(B8 50 BC 08 00)
Issuer Action Code - Denial: hex(00 00 00 00 00)
Issuer Action Code - Online: hex(B8 70 BC 98 00)
Issuer Country Code (ISO 3166): 643 (hex(06 43))
Static Data Authentication Tag List: hex(82)
Card Risk Management Data Object List 1 (CDOL1): hex(9F 02 06 9F 03 06 .. 9F 7C 14)
Card Risk Management Data Object List 2 (CDOL2): hex(91 0A 8A .. 37 04 9F 4C 08)
Application Currency Code (ISO 4217): 643 (hex(06 43))
Track 2 Equivalent Data: hex(?? ?? .. ??)
Cardholder Name: STOLYAROV/SERGEY
Application Version Number: hex(00 02)
Application Currency Exponent: hex(02)
Certification Authority Public Key Index: hex(05)
Issuer Public Key Exponent: hex(03)
Issuer Public Key Remainder: hex(B9 D0 9F EC 05 2C 8A B3 A1 .. 67 25 9A CA D7 FF FE 1E 0D 51 D3 91)
Issuer Public Key Certificate: hex(74 BE C6 .. F5 F4 DC 76 5F 8D 55 DD)
Dynamic Data Authentication Data Object List (DDOL): hex(9F 37 04)
ICC Public Key Exponent: hex(03)
ICC Public Key Certificate: hex(4F A0 26 65 DC .. D6 33 EE 7B 23 4D 93)
❈ ❈ ❈

And to conclude the example, a diagram showing the file structure as seen by the reader:

EMV File System


  • DDF-Directory Definition File in EMV terminology, is a DF according to ISO / IEC 7816
  • AEF-Application Elementary File in EMV terminology, is an EF according to ISO / IEC 7816
  • PSEPayment System Environment, an optional DDF containing an EF with a list of installed payment applications
  • PSD-Payment System Directory is an EF containing the names of payment applications
  • ADF-Application Definition File in EMV terminology, is a DF according to ISO / IEC 7816
All DDF's are addressed by name only. EMV standard does not provide addressing by file ID.

The AEFS inside the ADF contain the data required for the transaction, they are addressed by the short SFI identifier, with numbers 1 to 10 allocated for the data defined in the EMV, numbers 11 to 20 allocated at the discretion of the payment system, and numbers 21 to 30 — to the specific bank that issued the card.

The full structure of the file system on the EMV card is described in the following sections:

  • EMV_v4. 3, Book 1, section 10 Files
  • EMV_v4. 3, Book 3, section 5.3 Files

Theory: ATR

In this section, I will describe the ATR structure in detail. As I said earlier, the ATR byte array (stands for Answer-To-Reset) is sent by the contact card during initialization and contains information necessary for correct initialization of the reader or the application behind the reader. ATR for contactless cards is generated by the reader itself within the PC/SC infrastructure, so that the program's work with cards looks uniform.

The main ATR specification for asynchronous cards is described in Section 8.2 Answer-to-Reset of the ISO/IEC 7816-3 standard, in addition, additional interpretations are also available in other standards, for example, in EMV_v4. 3, Book 1, section 8.2 Answer-to-Reset.

The logic for constructing an ATR for synchronous cards (i.e., SLE5542 memory cards) is different and is specified in ISO / IEC 7816-10, but in PC / SC, the ATR issued by the reader to the application program is encoded in a special way to correspond to the ATR structure described in ISO/IEC 7816-3 (and in this text).

I will not talk about the electrical component and will immediately move on to the structure.

The ATR is a byte array from size 2 to 33 inclusive. Each byte is encoded with one or more named values, and the bytes themselves also have names-designations:

ATR bytes


At the beginning there is a mandatory byte of the format-T0. Two values are encoded in it: the indicator Y1 and the number K as follows:

ATR T0 coding


The interface bytes TAI, tbi, tci, and TDI encode the protocols that the card offers and the parameters of these protocols. These bytes can be global, in which case they relate to all protocols or some card parameters in general; or they can be specific to a particular protocol.

TA i, TB i, TC i contain data, and TD i also determines whether the next interface bytes will be further in the array. Each byte tdi encodes the indicator yi+1 and the type T:

ATR TDi coding


The type T defines the protocol, it is a number in the range from 0 to 15 inclusive, at the moment only T=0 and T=1 are valid values. The protocol specified in TD i uses parameters from TA i+1, TB i+1, TC i+1. Protocols should be listed in ascending order of number. If TD 1 does not exist, then the protocol T=0 is assumed.

The four bits of each Yi indicator are flags that specify which interface bytes will follow next. Ifb5=1, then the TA i byte goes next, ifb6=1, then the TB i byte, and so on. For example, Y1=0101 means that there will be two bytes TA1 and TC1 next, and there will be no other bytes.

Obviously, with this encoding, there can be a maximum of 32 blocks: Y1 from T0 encodes only TD 1, Y2 from TD 1 encodes only TD 2, and so on until TD 32, which encodes Y 33=0, since no more bytes are allowed. This is a degenerate scheme, but it is syntactically correct.

If TD i is missing, it signals the end of the interface byte block and then there are historical bytes in the amount of K-this is the same number that was encoded in T0.

❈ ❈ ❈

The semantics of interface bytes is very complex and I will not describe it here, instead I recommend (in addition to ISO/IEC 7816-3) to read the wikipedia article (https://en.wikipedia.org/wiki/Answer_to_reset), where everything is described in great detail.

❈ ❈ ❈

Historical bytes are additional data that helps identify the card. The structure of this block is described in detail in ISO / IEC 7816-4, section 12.2.2 Historical bytes.

The first byte is the category indicator. It can take the following values:

0x00subsequent data consists of COMPACT-TLV encoded objects, plus three mandatory status indicator bytes at the end, see ISO / IEC 7816-4, section 12.2.2.11 Status indicator;0x10the next byte is a reference to the DIR data (to be honest, I have no idea what this means, it makes sense for synchronous memory cards, but there is a different ATR structure, so I just ignore it);0x80subsequent data consists of objects encoded in COMPACT-TLV, which may include a status indicator (BER-TLV tag 0x48, compact tag 0x81, 0x82or0x83).;0x81 - 0x8Freserved for future use;XXall other values indicate a proprietary backstory byte format that is not covered by the ISO/IEC 7816 standard.
The mentioned COMPACT-TLV is a way to encode cross-industry BER-TLV objects with a tag4X:

  • if the object tag is0x4X;
  • if the length of the data block is0x0Y;
  • that value 0xXYis called a compact tag, and the COMPACT-TLV object itself consists of the tag 0xXYand subsequent Ybytes.
Reverse decoding occurs similarly.

Tags 0x4Xare application class tags (i.e. their semantics are fixed) and they are defined in different parts of the ISO / IEC 7816 standard. Here they are all (RFU-reserved for future use, the tag value is not defined at the moment):

  • 0x40 — RFU
  • 0x41 — Country code and national data (ISO/IEC 7816-4, 12.2.2.3 Country or issuer indicator)
  • 0x42 — Issuer identification number (ISO/IEC 7816-4, 12.2.2.3 Country or issuer indicator)
  • 0x43 — Card service data (ISO/IEC 7816-4, 12.2.2.5 Card service data)
  • 0x44 — Initial access data (ISO/IEC 7816-4, 12.2.2.6 Initial access data)
  • 0x45 — Card issuer's data (ISO/IEC 7816-4, 12.2.2.7 Card issuer's data)
  • 0x46 — Pre-issuing data (ISO/IEC 7816-4, 12.2.2.8 Pre-issuing data)
  • 0x47 — Card capabilities (ISO/IEC 7816-4, 12.2.2.9 Card capabilities)
  • 0x48 — Status information (ISO/IEC 7816-4, 12.2.2.11 Status indicator)
  • 0x49 — RFU
  • 0x4A — RFU
  • 0x4B — RFU
  • 0x4C — RFU
  • 0x4D — Extended header list (ISO/IEC 7816-4, 8.4.5 Extended header and extended header list)
  • 0x4E — RFU
  • 0x4F — Application identifier (ISO/IEC 7816-4, 12.2.2.4 Application identifier)
You can find detailed semantics of these objects either in the specified parts of the standard, or in the example below.

❈ ❈ ❈

The TCK byte is optional and serves to check the integrity of the ATR. It contains the XOR of all bytes starting from T0 and ending with the byte before TCK.

In PC / SC, the first byte of the ATR is not T0, but TS. It is defined in ISO / IEC 7816-3 and sets the electrical / signal communication parameters. Even though TS is not included in the ATR in the standard, PC/SC still returns it. This should be taken into account when parsing.

example-13: Parsing the ATR

Source code of the program: example-13/atr-parse


For this example, you can use both types of readers: contactless and contact chip cards.
This example is of no practical value, since the ATR is intended for the reader that uses it to establish and configure a connection. But I still included it in the text to combine the description with the code for more productive study.

We will store the grouped bytes of the interface in separate objects of the class InterfaceBytes.

We start by parsing the format byte:

# check format byte T0
T0 = data[1]
Y = (T0 >> 4) & 0xF;
historicalBytesLength = T0 & 0xF
We use a single variable to store the current yi, since there is no need to store them all.

Next, we read all the interface bytes and write them to the list allInterfaceBytes. At the end of the loop, we update the variables T and Y with new values:

# read interface bytes
p = 2
T = 0
while True:
TA = None
TB = None
TC = None
TD = None
# check is next byte is TAi
if (Y & 1) != 0:
TA = data[p]
p += 1
# check is next byte is TBi
if (Y & 2) != 0:
TB = data[p];
p += 1
# check is next byte is TCi
if (Y & 4) != 0:
TC = data[p]
p += 1
# check is next byte is TDi
if (Y & 8) != 0:
TD = data[p]
p += 1
allInterfaceBytes.append(InterfaceBytes(TA, TB, TC, TD, T))

if TD is None:
break

T = TD & 0xF
Y = (TD >> 4) & 0xF
Selecting the background bytes:

historicalBytes = data[p : p + historicalBytesLength]
On the TCK check byte, we score and do not check it.

❈ ❈ ❈

Now you can print the data, let's start with the interface bytes:

# print interface bytes
print('Interface bytes:')
for i,b in enumerate(allInterfaceBytes, 1):
if b.TA is not None:
print(' TA{} = {:02X} (T = {})'.format(i, b.TA, b.T))
if b.TB is not None:
print(' TB{} = {:02X} (T = {})'.format(i, b.TB, b.T))
if b.TC is not None:
print(' TC{} = {:02X} (T = {})'.format(i, b.TC, b.T))
if b.TD is not None:
print(' TD{} = {:02X} (T = {})'.format(i, b.TD, b.T))
From the point of view of an external application, there is nothing particularly interesting in the interface bytes, they contain only data for the low-level protocol. But I will try to analyze the background bytes in more detail, since there is useful information there.

In ISO / IEC 7816-4, section 12.2.2 Historical bytes describes the rules for parsing prehistory bytes. In my code, I do this in the functions printHistoricalBytesValue, getStatusIndicatorBytes, getCapabilities(I don't give their code here, since it is very voluminous, see the atr-parse file).

print('Historical bytes length (K): {}'.format(historicalBytesLength))
print('Historical bytes (raw): {}'.format(toHexString(historicalBytes)))

if historicalBytes[0] == 0x80:
# parse all as COMPACT-TLV objects
p = 1
while True:
if p == historicalBytesLength:
break
if p > historicalBytesLength:
raise('Incorrect historical bytes structure.')
objLen = historicalBytes[p] & 0xF
objTag = ((historicalBytes[p] >> 4) & 0xF) + 0x40
objData = historicalBytes[p + 1 : p + objLen + 1]
printHistoricalBytesValue(objTag, objData)
p += objLen + 1

elif historicalBytes[0] == 0x0:
pass
else:
print('Proprietary historical bytes structure.');
❈ ❈ ❈

MTS mobile phone SIM card:

% ./atr-parse
Connected reader: ACS ACR 38U-CCID
Waiting for the card...
Inserted card with ATR: 3B 9F 95 80 1F 47 80 31 E0 73 36 21 13 57 4A 33 0E 10 31 41 00 B4
Interface bytes:
TA1 = 95 (T = 0)
TD1 = 80 (T = 0)
TD2 = 1F (T = 0)
TA3 = 47 (T = 15)
Historical bytes length (K): 15
Historical bytes (raw): 80 31 E0 73 36 21 13 57 4A 33 0E 10 31 41 00
TAG: 43; DATA: E0
Card service data:
Application selection by full DF name: yes
Application selection by partial DF name: yes
BER-TLV data objects in EF.DIR: yes
BER-TLV data objects in EF.ATR: no
EF.DIR and EF.ATR access services: by the READ RECORD (S) command (record structure)
Card with MF
TAG: 47; DATA: 36 21 13
Card capabilities
DF selection: by path, by file identifier
Short EF identifier supported: yes
Record number supported: yes
Record identifier supported: no
EFs of TLV structure supported: no
Behaviour of write functions: Proprietary
Value 'FF' for the first byte of BER-TLV tag fields: invalid
Commands chaining: no
Extended Lc and Le fields: no
Logical channel number assignment: by the card
Maximum number of logical channels: 4
TAG: 45; DATA: 4A 33 0E 10 31 41 00
Card issuer's data
Beeline mobile phone SIM card:

% ./atr-parse
Connected reader: ACS ACR 38U-CCID
Waiting for the card...
Inserted card with ATR: 3B 9F 94 80 1F C7 80 31 E0 73 FE 21 1B 57 3C 86 60 CD A1 00 12 46
Interface bytes:
TA1 = 94 (T = 0)
TD1 = 80 (T = 0)
TD2 = 1F (T = 0)
TA3 = C7 (T = 15)
Historical bytes length (K): 15
Historical bytes (raw): 80 31 E0 73 FE 21 1B 57 3C 86 60 CD A1 00 12
TAG: 43; DATA: E0
Card service data:
Application selection by full DF name: yes
Application selection by partial DF name: yes
BER-TLV data objects in EF.DIR: yes
BER-TLV data objects in EF.ATR: no
EF.DIR and EF.ATR access services: by the READ RECORD (S) command (record structure)
Card with MF
TAG: 47; DATA: FE 21 1B
Card capabilities
DF selection: by full DF name, by partial DF name, by path, by file identifier, Implicit DF selection
Short EF identifier supported: yes
Record number supported: yes
Record identifier supported: no
EFs of TLV structure supported: no
Behaviour of write functions: Proprietary
Value 'FF' for the first byte of BER-TLV tag fields: invalid
Commands chaining: no
Extended Lc and Le fields: no
Logical channel number assignment: by the interface device
Maximum number of logical channels: 4
TAG: 45; DATA: 3C 86 60 CD A1 00 12
Card issuer's data
WORLD card:

% ./atr-parse
Connected reader: ACS ACR 38U-CCID
Waiting for the card...
Inserted card with ATR: 3B 78 13 00 00 80 31 C0 72 F7 41 81 07
Interface bytes:
TA1 = 13 (T = 0)
TB1 = 00 (T = 0)
TC1 = 00 (T = 0)
Historical bytes length (K): 8
Historical bytes (raw): 80 31 C0 72 F7 41 81 07
TAG: 43; DATA: C0
Card service data:
Application selection by full DF name: yes
Application selection by partial DF name: yes
BER-TLV data objects in EF.DIR: no
BER-TLV data objects in EF.ATR: no
EF.DIR and EF.ATR access services: by the READ RECORD (S) command (record structure)
Card with MF
TAG: 47; DATA: F7 41
Card capabilities
DF selection: by full DF name, by partial DF name, by path, by file identifier
Short EF identifier supported: yes
Record number supported: yes
Record identifier supported: yes
EFs of TLV structure supported: no
Behaviour of write functions: Write OR
Value 'FF' for the first byte of BER-TLV tag fields: invalid
TAG: 48; DATA: 07
Status information:
LCS (life cycle status): Operational state (activated)
NTV+Satellite TV card:

% ./atr-parse
Connected reader: ACS ACR 38U-CCID
Waiting for the card...
Inserted card with ATR: 3F 77 18 00 00 C2 EB 41 02 6C 90 00
Interface bytes:
TA1 = 18 (T = 0)
TB1 = 00 (T = 0)
TC1 = 00 (T = 0)
Historical bytes length (K): 7
Historical bytes (raw): C2 EB 41 02 6C 90 00
Proprietary historical bytes structure.

Using the ISO/IEC 7816-4 file system as an example of UICC.

In cell phones and modems, smart cards are used to store user information (ID and secret key), which are usually called SIM cards, SIM is an abbreviation for Subscriber Identification Module. However, over time, the internal structure of these cards has significantly changed and expanded, and now they serve not only for data storage, but also partially for their secure processing. Subsequently, instead of a SIM card as a technical device, the term UICC — Universal integrated circuit card-was used, but informally UICC is still called SIM cards.

UICC/SIM cards meet the ISO/IEC 7816 family of standards both in electrical and software terms, but there is a specialized family of standards developed directly for the full specification by ETSI (European Telecommunications Standards Institute) and the 3GPP community (3rd Generation Partnership Project). There are a lot of versions and variants of standards, they are released quite often and, in addition to standardization, UICC is also responsible for the entire field of cellular communications in general. In this article, I will refer to specific versions of specifications as links to PDF documents. All specifications are freely available on the website https://www.3gpp.org/specifications-technologies/specifications-by-series.

UICC cards comply with the ISO / IEC 7816 standards, so you can apply the same algorithms and research methods to them as for EMV cards, for example. However, the physical format of UICC does not allow them to be used directly in a standard reader, so you will have to make a special adapter from a "large" SIM card, from which a regular, micro or nano SIM card usually breaks out. You should understand the idea from the photo:

SIM to ID-1 adapter


To hold the card, you can stick a piece of tape on the back side. For nano SIM, you need to additionally take the appropriate adapter.

⭐ I will sometimes use the word SIM instead of UICC, but in this article it is the classic SIM card that will not work, since UICC is much more powerful and functional than the old SIM cards, which were essentially simple memory cards. Many of the examples below simply won't work on older SIM cards, as they use earlier versions of the specifications.
❈ ❈ ❈

The standard document we are interested in is called ETSI TS 102 221 Release 15, Smart cards; UICC-Terminal interface; Physical and logical characteristics. You can download it freely: https://www.etsi.org/deliver/etsi_ts/102200_102299/102221/15.00.00_60/ts_102221v150000p.pdf (Release 15 at the time of writing). It describes the physical and communication aspects of UICC, in particular the APDU formats and ISO/IEC 7816 file system elements. In the future, I will refer to specific spec releases, even if newer versions were released at the time of reading.

Section 8.6 Reservation of file Ids of the ETSI TS 102 221 specification describes reserved identifiers of information objects that can be present on a UICC. And in the document ETSI TS 131 122 Release 17 there is a hierarchical scheme of them:

UICC reseved IDs


All these objects store information necessary for the operation of the cellular communication module. If you are interested, you can read a good overview presentation of SIM/USIM cards.

An MF with an identifier 0x3F00is described in ISO / IEC 7816-4. It is a dedicated file (DF) that represents the root of the hierarchy of all objects. It has a reserved name — Master File or MF, informally it is also called the root directory. Its special feature is that it is selected (by the SELECT statement) automatically after the card is initialized in the reader.

In UICC, there are several elementary files (EF) that store the actual data. Access to these files (both read and write) it can be limited to a PIN code. Elementary files in the diagram above are marked with a single border, and directories are marked with a double border.

❈ ❈ ❈

The C-APDU generation scheme is described in Section 10.1 Command APDU ETSI TS 102 221 Release 15. For compatibility, the new UICCs provide an interface like the old SIM cards. For them, the CLA class byte is equal A0, but we don't use this and we have the same class byte everywhere 00.

❈ ❈ ❈

When using the SELECT commandA4, you can specify what information about the file we want to receive in the response. There are three options in total: File Control Information (FCI), File Control Parameters (FCP), and File Management Data (FMD). Each of them has its own template with a dedicated tag to identify the template. The full use of the SELECT command for UICC is described in ETSI TS 102 221 Release 15, section 11.1.1 SELECT.

For UICC, only the FCP Template is used, so the C-APDU of selecting, say, MF will look like this:

CLA INS P1 P2 Lc DATA
00 A4 00 04 02 3F 00
In the argument P2, we specify 0x04whether it is in bit form 0 0 0 0 1 0 0. The meaning of bits is described in Table 11.2: Coding of P2 ETSI TS 102 221 Release 15.

In the DATA block, specify the MF-ID 0x3F00.

The result of this command is a block of data with the FCP Template encoded. This is a BER-TLV object with a tag0x62. It contains both cross-industry data elements (defined in ISO/IEC 7816-4, section 7.4.3 Control parameters) and UICC-specific data elements (described in section 11.1.1 SELECT of ETSI TS 102 221 Release 15).

⭐ If you use the protocolT=0, you will need a separate GET RESPONSE command to get data, which I described above in the section Processing the result of executing a command in the T=0 protocol. For very old SIM cards, the status word 61 XXwas used instead 9F XX.
Here is an example of parsing the result of the SELECT command to MF (FCP Template):

62 26 82 02 78 21 83 02 3F 00 A5 03 80 01 71 8A 01 05 8B 03 2F 06 0D C6 0F 90 01 70 83 01 01 83 01 81 83 01 0A 83 01 0B

0x62 // FCP Template
0x82: (RAW) 78 21 // File Descriptor
0x83: (RAW) 3F 00 // File Identifier
0xA5 // Proprietery information
0x80: (RAW) 71 // UICC characteristics, see [ETSI TS 102 221 Release 15], section 11.1.1.4.6.1
0x8A: (RAW) 05 // Life Cycle Status Integer
0x8B: (RAW) 2F 06 0D // Security attributes
0xC6: (RAW) 90 01 70 83 01 01 83 01 81 83 01 0A 83 01 0B // PIN Status Template Data Object
For a detailed description of each field, see ETSI TS 102 221 Release 15.

❈ ❈ ❈

Another important file is an EF with an identifier 0x2F00, also called EF.DIR. This file with a linear record structure lists all available apps on the card. Above, in the section Parsing the FCI template (get_AID_from_PSEFCI), I showed how records from EF are read with the READ RECORD command. And specifically for UICC, there will be an example in the next section.

For EF, you can also get data in the form of an FCP Template, this is done similarly via C-APDU:

00 A4 00 04 02 2F 00
Command result (possible objects are listed in section 11.1.1.3.2 Response for an EF ETSI TS 102 221 Release 15):

62 1A 82 05 42 21 00 20 01 83 02 2F 00 8A 01 05 8B 03 2F 06 07 80 02 00 20 88 01 F0

0x62
0x82: (RAW) 42 21 00 20 01 // File Descriptor
0x83: (RAW) 2F 00 // File Identifier
0x8A: (RAW) 05 // Life Cycle Status Integer
0x8B: (RAW) 2F 06 07 // Security attributes
0x80: (RAW) 00 20 // File size
0x88: (RAW) F0 // Short File Identifier (SFI)
❈ ❈ ❈

And for example, one more mandatory EF ICCID object, which is located directly under MF and has a fixed ID 0x2FE2. This file has a transparent structure, is read by the READ BINARY command, and contains a unique UICC identifier with a length of 10 bytes. This EF is described in Section 13.2 of the EFICCID(ICC Identification) ETSI TS 102 221 Release 15.

After selecting this file, the command 00 A4 00 00 02 2F E2calls READ BINARY (see section 11.1.3 READ BINARY ETSI TS 102 221 Release 15) 00 B0 00 00 0A(listing the program apdu-terminaloperation from the example above):

APDU% 00 A4 00 00 02 2F E2
> 00 A4 00 00 02 2F E2
< [empty response] Status: 90 00
APDU% 00 B0 00 00 0A
> 00 B0 00 00 0A
< 98 07 91 29 00 38 96 15 17 F8 Status: 90 00
Please note that when we selected the file, we did not request FCP template data (P1=0x00, P2=0x00 instead P2=0x04of), so we received a status word 0x9000and an empty data block in response.

The EF ICCID object stores a unique UICC number. It is also printed on each SIM card, on the reverse side where there are no contacts, as a group of digits in several lines. Also on the" big " ID-1 card, this number is printed as a barcode. In EF ICCID, the number is encoded as a Binary Encoded Decimal with left alignment and separator0xF, so the value 98 07 91 29 00 38 96 15 17 F8corresponds to the number 897019920083695171.

example-14: Reading data from UICC



For this example, you can only use the contact chip card reader plus a special adapter to "reach" the contacts of the UICC SIM card to the reader contacts.
The goals of this example are:

  • use the UICC example to demonstrate working with the ISO/IEC 7816 file system and various types of information objects;
  • parse and display data with UICC in a readable form.
First, we select and read the FCP for MF:

# select MF and fetch FCP
# CLA INS P1 P2 Lc DATA
apdu = '00 A4 00 04 02 3F 00'
response, sw1, sw2 = transmit_wrapper(cardservice.connection, toBytes(apdu))
if (sw1,sw2) != (0x90,0x00):
print('Failed to read MF FCP Template')
return 1

parts = bertlv.parse_bytes(response)
mf_fcp = bertlv.find_tag(0x62, parts)
if mf_fcp is None:
print('Failed to parse MF FCP Template')
return 1

print('MF FCP data:')
for tlv in mf_fcp.value:
if tlv.tag == 0x82:
print(' File descriptor: {}'.format(toHexString(tlv.value)))
elif tlv.tag == 0x83:
print(' File identifier: {}'.format(toHexString(tlv.value)))
elif tlv.tag == 0x84:
print(' DF name (AID): {}'.format(toHexString(tlv.value)))
elif tlv.tag == 0xA5:
print(' Proprietary information:')
for x in tlv.value:
print(' 0x{:X}: {}'.format(x.tag, toHexString(x.raw_value)))
elif tlv.tag == 0x8A:
print(' Life Cycle Status Integer: {}'.format(toHexString(tlv.value)))
elif tlv.tag in (0x8B, 0x8C, 0xAB):
print(' Security attributes: {}'.format(toHexString(tlv.value)))
elif tlv.tag == 0xC6:
print(' PIN Status Template DO: {}'.format(toHexString(tlv.value)))
elif tlv.tag == 0x81:
print(' Total file size: {}'.format(toHexString(tlv.value)))
else:
print(' 0x{X}: {}'.format(tlv.tag, tlv.raw_value))
The structure of all information objects is described in section 11.1.1 SELECT ETSI TS 102 221 Release 15. For example, an object with a tag 0x82(File descriptor) has rules for interpreting bytes, which include, for example, the size and number of records for an elementary (EF) linear file with records.

Next, we select an elementary EF.DIR file, in which all applications on the card are recorded, the FCP reading is about the same, so I don't give this code here.

Then we read the entries from EF. DIR.

# read records
efdir_records = []
while True:
# CLA INS P1 P2 Lc
apduBytes = toBytes('00 B2 00 02 00')
response, sw1, sw2 = transmit_wrapper(cardservice.connection, apduBytes)
if sw1 == 0x6C:
# set new Le and repeat command
apduBytes[4] = sw2
response, sw1, sw2 = transmit_wrapper(cardservice.connection, apduBytes)
if (sw1,sw2) == (0x6A,0x83):
break
elif (sw1,sw2) != (0x90,0x00):
print('Failed to read record')
break
parts = bertlv.parse_bytes(response)
app_record = bertlv.find_tag(0x61, parts)
if app_record is not None:
efdir_records.append(app_record)
We read records from the currently selected file with the READ RECORD command with the instruction code0xB2. In the argument P1, we specify 0x00(which means that we do not want to read a specific record, but sequentially all of them), in P2, we specify a value 0x02that means that we must read the next record at each call. At the first call, for each record, we pass it to the Lc field 0x00so 6A LLthat the record length is returned in the status word0xLL, then we specify this length in Lc and get the actual bytes.

In its raw form, the entry looks something like this:

61 14 4F 0C A0 00 00 00 87 10 02 FF FF FF FF 89 50 04 55 53 49 4D FF FF FF FF FF FF FF FF FF FF
The length of each record for this file is fixed, so the last bytes are given a value 0xFF. Information about what 0xFFis used to fill in the empty space is written in the File descriptor FCP field. It is encoded there as a special bit in the Data coding byte. The File descriptor also stores the type of records in the file (if there are any records at all), their number, and maximum length. We need to analyze all this for correct use, but I don't do this in the example.

Next, we print the contents of these records — these are all BER-TLV objects, each of which contains the application ID and its text label.

print('EF.DIR records:')
for i,tlv in enumerate(efdir_records, 1):
print(f'Record {i}')
for x in tlv.value:
if x.tag == 0x4F:
print(' AID: {}'.format(toHexString(x.value)))
elif x.tag == 0x50:
print(' Application label: {}'.format(HexListToBinString(x.value)))
else:
print(' 0x{:X}: {}'.format(x.tag, toHexString(x.raw_value)))
❈ ❈ ❈

The result of the program looks something like this on the example of the beeline card:

% ./uicc-read
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected.
MF FCP data:
File descriptor: 78 21
File identifier: 3F 00
Proprietary information:
0x80: 71
Life Cycle Status Integer: 05
Security attributes: 2F 06 0D
PIN Status Template DO: 90 01 70 83 01 01 83 01 81 83 01 0A 83 01 0B

EF.DIR:
File descriptor: 42 21 00 20 01
File identifier: 2F 00
Life Cycle Status Integer: 05
Security attributes: 2F 06 07
File size: 00 20
Short file identifier: F0
EF.DIR records:
Record 1
AID: A0 00 00 00 87 10 02 FF FF FF FF 89
Application label: USIM
And so on the example of the Tinkoff Mobile card (there are already two applications in EF.DIR):

% ./uicc-read
Connected reader: ACS ACR 38U-CCID
Waiting for card ...
Card connected.
MF FCP data:
File descriptor: 78 21
File identifier: 3F 00
Proprietary information:
0x80: 61
0x87: 01
0x83: 00 06 15 00
Life Cycle Status Integer: 05
Security attributes: 2F 06 01 0B 00 0B
PIN Status Template DO: 90 01 40 83 01 01 83 01 0A
Total file size: FF FF

EF.DIR:
File descriptor: 42 21 00 40 02
File identifier: 2F 00
Proprietary information:
0xC0: 40
Life Cycle Status Integer: 05
Security attributes: 2F 06 01 03 00 03
File size: 00 80
Total file size: 00 94
Short file identifier: F0
EF.DIR records:
Record 1
AID: A0 00 00 00 87 10 02 FF FF F0 01 89 00 00 01 FF
Application label: USIM
0x73: A0 10 80 03 17 12 12 81 04 5F 40 5F 30 82 03 45 41 50
Record 2
AID: A0 00 00 00 87 10 04 FF FF F0 01 89 00 00 01 FF
Application label: ISIM
❈ ❈ ❈

For a detailed study of the UICC card contents, I recommend the pySim project, which is a python-based set of tools for thoroughly studying and modifying SIM and UICC cards. In particular, there is an interactive shell that allows you to conveniently "walk" through the structure of smart card UICC files.

Conclusion

At this point, I want to finish the article and summarize it. My main goal was to review technologies and standards for the practical use of smart cards. The most important points that you have tried to convey:
  • PC/SC as the main communication library and its interface as a pyscard library;
  • APDU as a way to communicate with the card from an external application;
  • key industry standards;
  • principles of working with data on a smart card;
  • object encoding (TLV, SIMPLE-TLV, BER-TLV).

Links

Datasheets and manuals


Standards and specifications


References

  • Bank microprocessor cards Goldovsky I. M. / / ISBN 978-5-9614-1233-8-a great book, I strongly recommend it, it describes in detail the technologies and methods of working with bank cards.
  • Smart Card Application Development Using Java Uwe Hansmann, Martin S. Nicklous, etc-a book on how to write smart card applications using the OpenCard Framework.
  • Smart Card Programming Ugo Chirico, the book is about the same thing as this article, only much more detailed and in English
  • Smart Card Handbook Wolfgang Rankl, Wolfgang Effing — a very detailed book with a thousand pages about smart card technologies, protocols, cryptography and all other aspects, almost an encyclopedia
  • Business Encyclopedia "Payment cards" Goldovsky I. M., etc / / ISBN 978-5-406-03339-5-a book about the history (including Russian) of bank cards

Tools, sites, and software

  • EMV TLV Parser is a convenient online parser for BER-TLV data.
  • ACS Script Tool is a desktop program from ACS for sending APDUs to the reader and automating this process. There are versions for Windows, Linux and Macos.
  • EFTlab — a large knowledge base on EMV: codes and names of objects, identifiers, and other information elements.
  • Smart card ATR parsing — parsing ATRs online.
  • ASN. 1 Complete — Complete guide to ASN. 1
  • pySim-a set of tools for viewing and editing UICC / SIM cards and its documentation
 
Top