[Recycling Thursday] Reading NFC MiFare cards

I've been piling a lot of junk boards, sensors and electronic things recently and since I have some time, I decided I might try to revive some of these old items to do something (quite) useful !

They are primarily v0.1 boards from kickstarter campaigns, old versions of shields and sensors that were purchased a long time ago and that didn't quite fit the need at the time.

Today, we'll be looking at NFC tech with a 2012 NFC reader from Elec Freaks. It's still selling today on their website, and it works quite well so it's worth it may be worth the price.

The shield is a standard NFC Reader (13.56 MHz). NFC itself is a sub part of the RFID spec.

Several NFC readers can read RFID HF tags (that also use the 13.56 MHz band) if they comply to ISO 15693, which this board does not apparently since it's based on the PN352/C1 chip that does only support the following modes :

  • ISO 14443A / Mifare, Felica
  • ISO 14443B
  • NFCIP-1

See the actual datasheet of the PN532/C1 chip here.

You can find the original Shield's user guide on their website too.

Which library to use ?

So we've got this shield, we've got an Arduino Uno too, we plug them together and hook this to our setup, and open the Arduino IDE.

What next ?

We need a library to interact with the PN532 chip. This shield is SPI so that's going to be pretty straightforward.

ElecFreaks recommends its own "PN532_SPI" lib but we prefer the great Adafruit PN532 lib (https://github.com/adafruit/Adafruit-PN532/) that is well documented and has nice examples. It doesn't support Felica but as it's not really used here in France, I don't have a strong rationale at looking for compatibility for this type of card.

The code

Importing is quite easy, we need the SPI library and the Adafruit PN532, and then we declare the nfc reader using the software SPI available on the UNO (pins 10 → 13) :

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>

#define PN532_SCK  (13)
#define PN532_MOSI (11)
#define PN532_SS   (10)
#define PN532_MISO (12)

// Software SPI connection:
Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);

to setup the whole thing, we need to initiate a connection, and then configure the board. There is a lot of info we can retrieve from the chip so we know what version it is :

void setup() {
  Serial.begin(115200);
  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (!versiondata) {
    Serial.print(F("Didn't find PN53x board, quitting"));
    while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print(F("Chip PN5")); Serial.print((versiondata>>24) & 0xFF, HEX); 
  Serial.print(F(" - Firmware v")); Serial.print((versiondata>>16) & 0xFF, DEC); 
  Serial.print(F(".")); Serial.println((versiondata>>8) & 0xFF, DEC);
  
  // configure board to read RFID tags
  nfc.SAMConfig();
  Serial.println(F("Waiting for an ISO14443A Card ..."));
}

Which would produce something along those lines (with the NFC Shield 1.6) :

Chip PN532 - Firmware v1.6
Waiting for an ISO14443A Card ...

We now have to loop until we find a card in the vicinity. This is pretty simple with the helpful readPassiveTargetID() function from the Adafruit lib :

void loop() {
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
    
  // Wait for an ISO14443A type cards (Mifare, etc.).  When one is found
  // 'uid' will be populated with the UID
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  
  if (success) {
    // Display some basic information about the card
    Serial.print(F("\nISO14443A card: ")); nfc.PrintHex(uid, uidLength);
  }
}

This will simply print the UID of the card in Hex :

ISO14443A card: 0xCE 0xB7 0xF9 0xBC

Beware ! If you try to println() the UID as a base 10 number, you might encounter data length problems for certains cards which UID length is too big for the print() function (long long int will not work for instance) on the Arduino

Different types of Mifare cards

MIFARE is the NXP Semiconductors-owned trademark of a series of chips widely used in contactless smart cards and proximity cards (source: wikipedia).

There are a lot of different types of Mifare cards out there but I have access to two known types of tags :

  • Mifare classic
  • Mifare Ultralight
  • + Various RFID / NFC cards in my wallet, that I'll test afterwards.

In all the data blocks present on these cards, the first blocks are usually the manufacturer blocks, containing the serial number of the card (read previously) and some other data.

The Mifare Classic card has a 4-byte UID, whereas the Mifare Ultralight has a 7 byte UID. That's how you tell them apart.

Let's see how we read the data from them.

Classic

There are two variants (1k or 4k EEPROM), but on the 1k version, there is 64 blocks of 16 bytes each, so 1024 bytes of data total.

The manufacturer block is the block 0, which we'll skip when we read the data here.

Each block must be authenticated with either the key A or B, present in the first block of each 4-block sector (see https://firefart.at/post/how-to-crack-mifare-classic-cards/ for a post on how to crack the authentication — the Wikipedia page also has a comprehensive paragraph on the subject).

// Read and dump a Mifare Classic card (UID is 4 bytes)
void readMifareClassicTag(uint8_t uid[], uint8_t uidLength) {
  uint8_t success;
  uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
  uint8_t keyb[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  uint8_t data[16];
  char dataline[12];
  
  // Skipping manufacturer block
  for (uint8_t blockn = 1; blockn < 64; blockn++)
  {
    success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, blockn, 1, keya);
    
    if (success)
    {
      success = nfc.mifareclassic_ReadDataBlock(blockn, data);
    
      if (success)
      {
        // Data seems to have been read ... spit it out
        sprintf(dataline, "Block %02d : ", blockn);
        Serial.print(dataline);

        if (((blockn + 1) % 4) == 0) {
            Serial.print(F("[ Sector ] "));
        } else {
            Serial.print(F("[  Data  ] "));
        }
        
        nfc.PrintHexChar(data, 16);

      } else {
        Serial.println(F("[unable to read .. quitting]"));
        return;
      } 
    } else {
      Serial.println(F("[unable to authenticate .. quitting]"));
      return;
    } 
  }
}
Block 01 : [  Data  ] 14 01 03 E1 03 E1 03 E1 03 E1 03 E1 03 E1 03 E1  ...�.�.�.�.�.�.�
Block 02 : [  Data  ] 03 E1 03 E1 03 E1 03 E1 03 E1 03 E1 03 E1 03 E1  .�.�.�.�.�.�.�.�
Block 03 : [ Sector ] 00 00 00 00 00 00 78 77 88 C1 00 00 00 00 00 00  ......xw��......
Block 04 : [  Data  ] 00 00 03 3E 92 04 12 77 38 2F 31 63 6F 6D 2E 61  ...>�..w8/1com.a
...
Block 58 : [  Data  ] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
Block 59 : [ Sector ] 00 00 00 00 00 00 7F 07 88 40 00 00 00 00 00 00  .......�@......
Block 60 : [  Data  ] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
Block 61 : [  Data  ] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
Block 62 : [  Data  ] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
Block 63 : [ Sector ] 00 00 00 00 00 00 7F 07 88 40 00 00 00 00 00 00  .......�@......

Ultralight

The 512-bit card EEPROM memory is organised in 16 pages with 4 bytes per page.

In the case of the Ultralight card, the first four pages are manufacturer data, so we don't really need to read that a second time (we suppose we already read the UID in our code).

So there are 12 pages left where we can store (and read) data.
Many ultralight cards have no authentication of any sort on them, so we'll just dump what we read with the helper function :

// Read and dump a Mifare Ultralight tag (UID is 7 bytes)
void readMifareUltralightTag() {
  uint8_t success;
  uint8_t data[32];
  char dataline[12];

  // Iterate through interesting pages (4 to 15)
  // http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf
  for (int currentpage = 4; currentpage < 16; currentpage++) {
    // Dump the data into the 'data' array
    success = nfc.mifareultralight_ReadPage(currentpage, data);
    sprintf(dataline, "Page %02d : ", currentpage);
    Serial.print(dataline);
    if (success) {
      nfc.PrintHexChar(data, 4);
    } else {
      Serial.println(F("[unable to read .. quitting]"));
      return;
    }
  }
}

Easy enough, we get a simple result, for instance :

Page 04 : 01 03 A0 0C  ..�.
Page 05 : 34 03 00 FE  4..�
Page 06 : 00 00 00 00  ....
Page 07 : 00 00 00 00  ....
Page 08 : 00 00 00 00  ....
Page 09 : 00 00 00 00  ....
Page 10 : 00 00 00 00  ....
Page 11 : 00 00 00 00  ....
Page 12 : 00 00 00 00  ....
Page 13 : 00 00 00 00  ....
Page 14 : 00 00 00 00  ....
Page 15 : 00 00 00 00  ....

Writing is super easy with the helper functions again :

memcpy(data, (const uint8_t[]){ 'f', 'o', 'o', 'b', 'a', 'r', 'f', 'l', 'i', 'e', 's', '.', 'i', 'o', 0, 0}, sizeof data);
success = nfc.mifareclassic_WriteDataBlock (4, data);

In case you didn't follow, the full code is available here on Github : https://github.com/tchapi/arduino-nfc-card-reader

Contactless payment Credit card

Wikipeda states that "The various standards emerging are local in focus and are not compatible, though the MIFARE Classic card from Philips has a large market share in the United States and Europe".

The ones I have seem to be Ultralight Mifare cards (7-byte UID) [In France at least]. Both cards I tried had distinct ID but I could not get the data from them.


Now it's time to find something useful to do with that !