Introduction: Using Mifare Ultralight C With RC522 on Arduino

Using RFID technology to identify card holders or to authorize to do something (open a door etc.) is a fairly common approach. In case of DIY application the RC522 module is widely used as it is quite cheap and a lot of code exists for this module.

In most cases, the UID of the card is used to “identify” the card holder, and Mifare Classic cards are used as they are cheap and often included when buying a RC522 module.

But as you might know, the Mifare Classic system has been hacked for some years and it is not considered as safe anymore. The encryption system Crypto1 used by Classic cards can be overcome and the are re-writable cards where data an UID can be reprogrammed (magic cards).

So for any security relevant application the use of Mifare Classic cards is not recommended! The same applies for (most) NTAG and Mifare Ultralight systems

So the choice is either to use a professional system or trying to use a more secure RFID system. Available systems are Mifare Ultralight C, Mifare DESFire and Mifare Plus. As there are many professional systems using these more secure systems, for the DIY community there are virtually no solutions (there is one Teensy based DESFire solution, which is based in the more expensive PN523 breakout board). Additionally the DESFire cards are pretty expensive. So the challenge was to find a better and cheaper solution.

The presented solution provides full access to the cheap Mifare Ultralight “C” cards using the cheap Chinese RC522 DIY module. Based on this code, the secure Mifare Ultralight C can be used in DIY applications.

Step 1: Preconditions

Although the RC522 is well designed, it is in most cases poorly build as some components are poorly dimensioned. This leads to the bad reputation of the module that it has low sensitivity and not all type of cards will be identified. Especially the Mifare Ultralight C will neither be identified nor will it be possible to read the cards.

The main problem is the specification of the inductors L1 and L2. As described on http://ham.marsik.org/2017/04/using-cheap-rc522-nfc-reader-to-read.html. Just by replacing these inductors to appropriate ones e.g. FERROCORE CW1008-2200 suddenly the RC522 shows what its real potential is.

So prior to trying the given code, you MUST REPLACE the inductors. It just will not work with the pre-installed inductors!

The background of all this is, that the Ultralight C cards are quite energy hungry. This energy is provided by the RC522 RF-field. Due to low amperage of the inductors, the energy field is just not powerfull enough to power the Ultralight C. Other cards like the Mifare Classic just need less power and therefore work pretty stable.

Step 2: How Does It Work?

So after modifying the RC522 module, how can you use the Mifare Ulralight C for your application?

The trick is, that Mifare Ultralight C supports a password authentication based on the 3DES cipher. By using this password, the content of the card can be made “read only” or completely invisible to an unauthorized user.

In order to use this password protection the password needs to be written to the card and the pages needs to be protected. Once done, you can verify the card in your application either by just asking for a password based authentication or additionally ready data from a protected area. Only if this is successful you know that you can trust the provided UID on the card.

Beware: without the password based authentication you still cannot trust a Mifare Ultralight C card, as there are “magic cards” as well who simulate the Ultralight C.

Each card independent from the technology (if in the correct frequency) will respond with their UID when powered by the RF-field and request to identify themselves. Additionally they provide an SAK value providing minimal information about the type of card being present. Unfortunately all Mifare Ultralight and NTAG identify as the syme type (SAK=0x00), including the Mifare Ultralight C. So when polling for cards, at least the SAK value of 0x00 will give a hint that there might be a Ultralight C on the reader.

To make sure it is an Ultralight C a request for encrypted authentication can be send to the card. If this is NOT a Ultralight C card, this request will not be understood, and the response will be an NAK (not-acknolege).

If this is a Ulralight C card, you will get an 8 byte answer. These 8 Bytes are a random number “B” (RndB) encrypted by the stored key on the card using the 3DES cipher.

This encrypted RndB must be decrypted using the same key in the program. This random number is then slightly modified (rotated by one byte → byte 1 will be moved to byte 8 and all other bytes are pushed one byte lower, then called RndB’). The program then generates an 8 Byte random number "A" itself (RndA) and attaches this RndA to the modified RndB’. This is again encrypted using the key and send to the card.

The card decrypts the message and checks if RndB’ fits to the previously generated RndB on the card. If they match, the card now knows, that the program knows the key.

At this point, the program still does not know if the card knows the key and therefore can be trusted or not. To achieve this, the card now rotates the decrypted RndA by one byte, then encrypts these bytes using the key and sends them back.

The program will then decrypt the reply of the card and check if the original RndA and the replied RndA match. ONLY THEN both entities (program and card) know that they share the knowledge of the same key.

This process is only be used to authenticate. All further communication is always in “clear text”.

Although there are “magic Ultralight C” cards where the UID can be modified, the key itself can not be obtained from the card and the 3DES cipher is fairly secure. The key is a 16 Byte key, so a brute force approach to obtain the key will take some time.

As stated, the communication prior to authentication and after authentication is always in clear text (aka not encrypted). When writing a new key to the card, the content of the key can be sniffed out by using the right equipment. So please write the key only in a secure environment and keep the key secret.

When using the Ultralight C card

The Ultralight C card has multiple security features build in:

  1. One Time Programming (OTP) memory. In this area bits can be written, bus not deleted.
  2. A 16 bit one way counter. This counter can only increment, when acessed.
  3. A “write” or “read/write” protection of pages in the memory. Only if authenticated with the key, these pages can be read or modified.
  4. A freezing / blocking of individual pages to protect against any modification.

Neither the use of OTP, the 16 bit counter nor the use of the blocking bit is implemented in the given code, but can easily implemented based on the information given in https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pd...

As the protection by key is essential for using the Mifare Ultralight C, all relevant functions are present.

All commands are used in the Serial monitor with "new line only" and with 115200 Baud

  • “auth 49454D4B41455242214E4143554F5946” will request a authentication with the given key (in this case the standard Mifare Ultralight C key)
  • “dump” will dump the content of the card as far as they are visible. In case pages are protected by the key, these pages might not be visible until a previous authentication with key. In the first two columns it is indicated if pages are locked or access is restricted.
  • “newKey 49454D4B41455242214E4143554F5946” will write a new key to the card. The key is written to the pages 44 to 47. This will only work, if these pages are neither locked nor protected without a previous authentication.
  • "wchar 10 hello world” will write “hello world” starting from page 10. Again, this only works of the pages are neither locked nor protected without a previous authentication. When trying to write above page 39 or below page 4 this will prompt an error or data is ignored as these pages is not user memory.
  • “whex 045ACBF44688” will write Hex values direct to the memory, previous conditions apply.
  • “protect 30” protects all the pages from page 30 upward. Depending on the permission, these pages can then only be modified or read after a prior authentication with key. Using “protect” with values higher than 47 will set all pages to “unprotected” INCLUDING THE KEY at pages 44-47 (which than can only be modified but not read). In order to prevent altering the key, the protection should at least start at page 44.
  • “setpbit 0” sets the protection bit and decides if the protected pages are read only (“setpbit 1”) or can neither be read not written (“setpbit 0”) without a previous authentication with key.

Not all commands can be used immediately after the card was detected. A "dump" previously to an other command always helps.

Step 3: Important

  1. The program differentiates between the Ultralight types by reading page 43 and 44. If page 43 is readable and page 44 not, it is most likely a Ultralight C. BUT, if you read/write protect the page 43 the card is not anymore recognized as Ultralight C (does not have any effect on anything) The proper identification of the Ultralight should be done via the authentication with key (I did not implement that due to stability reasons).
  2. Prior to using the commands “setpbit” and “protect” the command “dump” must be used, otherwise the protection status of the pages will not be known.
  3. If you “read/write” protect the first pages of your card, it will not work anymore with this program as the first page is read constantly to see if there is still a card present. As the first two pages are read only anyway (the UID is stored there), there is no sense in protecting them.


Stability issues.

This code uses the “standard” RC522 library for Arduino and a 3DES library from https://github.com/Octoate/ArduinoDES. Whereas the RC522 library is quite commonly used, the 3DES library seems not so widespread and must be installed manually.

The code has been tested on an Arduino Uno. But while writing it, I encountered many weird problems in regard to stability. Somehow either my programming skills are not that good, one of the used libraries is unstable or mixing the libraries is not a good idea.

Please bear this in mind when using the code!!!

Changing it or using only parts of it can lead to strange behavior like crashing, printing weird things or getting timeouts or NAK when reading from the card. This can happen at any place in the code (it cost me many hours of debugging). If you find the reason(s) for this, please give me a hint.