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:
- Calculating odd parity over the plaintext
- Encrypting the calculated value with a single bit of keystream
- Not advancing the SR after encrypting this bit!
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:
- UID =
0x8972DB80 - Key =
0xFFFFFFFFFFFF - Reader Challenge =
0x99999999 - The tag has been selected by the standard ISO14443A Select sequence
| 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:
- Try to find some tags that have the static random number generator.
- Many MIFARE Classic clones have random number generators that always generate the same number
- Although this is bad from a security perspective, it makes testing the cryptography much easier
- If using a Proxmark for reference, it's quite simple to modify the MIFARE Classic driver to always pick a static reader challenge
- In the file armsrc/mifareutil.c, there is a function named "mifare_classic_authex_cmd.
- Near to the top of this file, 'nr' is calculated, which is the reader challenge.
- This can be replaced by a fixed constant, and the PM3 ARM client rebuilt.
- Initially, pick symmetrical (both word-wise and byte-wise) values for the key and reader challenge.
- The endianness and bit ordering is a bit odd, so having values that are symmetrical removes one possible source of error.
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:
- MIFARE Classic key management
- To reduce the chance of making tags unusable, the tagMifareClassic_safeBlockWrite will refuse to write to the trailer of any sector (where the keys are stored).
- MIFARE Classic value (decrement / increment / transfer / restore)
- CR95HF Unit tests
- CR95HF error handling
To port it to another RFID reader, you'll need to provide implementations of the following (see rfid_generic.h):
rfidGeneric_fn_sendFrameWaitForReplyExtParity:
- Sends sendBufLen words to the device (with parity in bit 15) and return up to recvBufLen words.
- The return value should equal the number of words received, or < 0 to indicate an error.
- Used for all MIFARE functionality
rfidGeneric_fn_sendFrameWaitForReply:
- Sends sendBufLen bytes to the device (with parity calculated by the RFID reader IC) and return up to recvBufLen bytes.
- The return value should equal the number of bytes received, or < 0 to indicate an error.
- Mostly used by the ISO14443A Select