MIFARE Classic Driver

For a challenge, I thought it'd be interesting to try to build a generic MIFARE CRYPTO1 driver that can be easily ported between RFID transceiver ICs.

It should be noted that MIFARE Classic is considered very insecure at this point and should not be used for anything security-sensitive, but it's still easy to buy tags that use it.

Additionally, the parity bit handling differs from standard ISO14443A parity bits, which means that whichever reader IC is selected must support user-provided parity bits.

The CR95HF from ST does support user-provided parity bits, described as "Parity Framing" in the datasheet. It also accepts these parity bits in a convenient format, where one byte of data is followed by one byte for the parity (of which only one bit is used).

As each data + parity word is 16 bits, it's very simple to send the data to the CR95HF:

tx_buffer[(i * 2) + 0] = data[i];

tx_buffer[(i * 2) + 1] = parity[i]; //parity is either 0x80 or 0x00

The ST25R391x series of ICs also support user-provided parity bits, but in a much less convenient way.

These devices provide a "stream mode", but this requires considering the data + parity as a 9-bit value which is then packed into 8-bit SPI frames.

CRYPTO1 Notes

CRYPTO1 has been well documented elsewhere so I won't write too much here, except to discuss the parity bits.

Parity bit handling in CRYPTO1 does not match the ISO14443A standard.

Instead, parity should be calculated by:

My implementation does not check the parity on the returned frames, but as all of the read data values are protected by the ISO14443A CRC (which is a 16 bit CRC for 16 bytes of data), the chance of missing errors due this is is low.

MIFARE Classic Authentication Process

Within these examples, I've written 16-bit values for the data to show where the parity bit is set (0x80xx) or clear (0x00xx)

The following example assumes the values:

Step Source Data Notes
1 Reader 0x8060 0x0004 0x80D1 0x003D Authenticate block 4 with key A. CRC = 0x3DD1
2 Tag 0x8000 0x8090 0x0080 0x00A2 Tag Challenge is 0x009080A2
3 Reader Calculates Ks0 = 0xFF2F6DBD , Ks1 = 0xE99FE6B0
Reader 0x0070 0x8006 0x807F 0x8029 0x0071 0x002F 0x0005 0x8056 encReaderChallenge = 0x70067F29, readerReply = 0xB172DED3, encReaderReply = 0x712F0556
4 Tag 0x80D9 0x0039 0x0015 0x80D6 encTagReply = 0xD93915D6
5 Reader Decrypts tag reply, = 0xCC1B98DE. Matches expected tag reply.

In detail:

1: Initially (after the ISO14443A select sequence), the reader sends an authentication request to the tag, with a specified block number and key (A or B).

2: The tag replies with a random 32 bit challenge.

3: The cipher shift register SR is initialised with the key (noting that the endianness needs to be swapped from big-endian to little-endian).

Next, 32 bits are shifted into SR, with linear feedback enabled. The 32 bits shifted in are the XOR of the 32 bit UID and the 32 bit tag challenge, both big-endian. The output of this shift process is Keystream 0, or Ks0 for short.

After this, the 32 bit reader challenge (in big-endian format) is shifted into the SR (again with linear feedback enabled). The output of the SR is Ks1.

This Ks1 value is then XOR'd with the reader challenge to form an encrypted reader challenge.

Then, the reader reply is calculated by providing the tag challenge as the seed to a different LFSR, and calculating the value 64 shift cycles later.

The reader reply is encrypted (by shifting 0s into the SR with linear feedback, and XORing the output of the SR with the reply), and the concatenation of encrypted reader challenge and encrypted reader reply are sent to the tag.

This is all implemented within the function mifareCrypto1_authReaderInit;

4: Using an LFSR with the same polynomial as used to calculate the reader reply, the tag calculates the tag reply based on the tag challenge 96 shift cycles later.

This value is then encrypted (with the same encryption process as for the reader reply) and sent to the reader

5: The reader decrypts the tag reply, and also locally calculates the tag challenge shifted 96 cycles.

If the decrypted tag reply matches the reader's local calculation, the authentication has succeeded.

This is implemented in mifareCrypto1_authReaderHandleTagReply.

Driver

Some additional test vectors can be found in the ZIP file at the end of this post, in test/rf/rfid/test_mifare_crypto1.c and test/rf/rfid/test_tag_mifare_classic.c.

The unit tests are written for the "Unity" unit test framework.

For anyone trying to implement CRYPTO1 themselves, I would very strongly advise a few things:

The following file contains the CRYPTO1, MIFARE Classic and CR95HF drivers, plus unit tests for CRYPTO1 and MIFARE Classic (written in the Unity test framework)

Items not yet implemented:

To port it to another RFID reader, you'll need to provide implementations of the following (see rfid_generic.h):

rfidGeneric_fn_sendFrameWaitForReplyExtParity:

rfidGeneric_fn_sendFrameWaitForReply: