I am working on the Mifare DESFire support in libfreefare and authentication is one of the only function done so far.
This page provides interesting information I used to achieve authentication. The fact that NXP teaks well-known algorithms to turn them into some weird nxp-specific ones is a PITA...
The code currently in my local copy of the repo only handles DES encryption but I guess that supporting 3DES should not be a big deal of work. However, before testing it, implementing support for all Mifare DESFire functions is a requirement. This week is scheduled to be a hack week on libfreefare's Mifare DESFire support for me, I hope things will go well.
#define DESFIRE_TRANSCEIVE(tag, msg, msg_len, res, res_len) \
do { \
if (!(nfc_initiator_transceive_dep_bytes (tag->device, msg, msg_len, res, &res_len))) \
return errno = EIO, -1; \
if ((1 == res_len) && (OPERATION_OK != res[0])) \
return MIFARE_DESFIRE(tag)->last_picc_error = res[0], -1; \
} while (0)
static void
xor8 (uint8_t *ivect, uint8_t *data)
{
for (int i = 0; i < 8; i++) {
data[i] ^= ivect[i];
}
}
void
rol8(uint8_t *data)
{
uint8_t first = data[0];
for (int i = 0; i < 7; i++) {
data[i] = data[i+1];
}
data[7] = first;
}
void
mifare_cbc_des (uint8_t *key, uint8_t *data, uint8_t *ivect, MifareDirection direction)
{
/*
* FIXME Should we change the way errors traverse this function?
*/
uint8_t ovect[8];
if (direction == MD_SEND) {
xor8 (ivect, data);
} else {
memcpy (ovect, data, 8);
}
DES_key_schedule ks;
DES_set_key ((DES_cblock *)key, &ks);
uint8_t edata[8];
DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &ks, DES_DECRYPT);
// stat = ecb_crypt (key, data, 8, DES_HW | DES_DECRYPT);
//
memcpy (data, edata, 8);
if (direction == MD_SEND) {
memcpy (ivect, data, 8);
} else {
xor8 (ivect, data);
memcpy (ivect, ovect, 8);
}
}
int
mifare_desfire_authenticate (MifareTag tag, uint8_t key_no)
{
ASSERT_ACTIVE (tag);
ASSERT_MIFARE_DESFIRE (tag);
MIFARE_DESFIRE(tag)->last_picc_error = OPERATION_OK;
uint8_t key[8];
memset (key, '\0', sizeof (key));
#if 0
/*
* Parity bits of DES keys are used for key versionning.
*/
DES_set_odd_parity ((DES_cblock *)&key);
#endif
uint8_t ivec[8];
memset (ivec, '\0', sizeof (ivec));
hexdump (key, 8, "key ", 0);
hexdump (ivec, 8, "ivec ", 0);
uint8_t command[2];
command[0] = 0x0A;
command[1] = key_no;
uint8_t status[9];
size_t n;
#if 0
hexdump (command, sizeof (command), "<--- ", 0);
if (!(nfc_initiator_transceive_dep_bytes (tag->device, command, sizeof (command), status, &n))) {
tag->active = false; /* Tag is no more active if authentication failed. */
errno = EIO;
return -1;
}
if (n == 1) {
tag->last_picc_error = status[0];
return -1;
}
hexdump (status, n, "---> ", 0);
#endif
DESFIRE_TRANSCEIVE (tag, command, sizeof (command), status, n);
uint8_t PICC_E_RndB[8];
memcpy (PICC_E_RndB, status+1, 8);
hexdump (PICC_E_RndB, sizeof (PICC_E_RndB), "e(PICC_RndB) ", 0);
uint8_t PICC_RndB[8];
memcpy (PICC_RndB, PICC_E_RndB, 8);
mifare_cbc_des (key, PICC_RndB, ivec, MD_RECEIVE);
hexdump (PICC_RndB, sizeof (PICC_RndB), " PICC_RndB ", 0);
uint8_t PCD_RndA[8];
DES_random_key ((DES_cblock*)&PCD_RndA);
hexdump (PCD_RndA, sizeof (PCD_RndA), " PCD_RndA ", 0);
uint8_t PCD_r_RndB[8];
memcpy (PCD_r_RndB, PICC_RndB, 8);
rol8 (PCD_r_RndB);
uint8_t token[16];
memcpy (token, PCD_RndA, 8);
memcpy (token+8, PCD_r_RndB, 8);
hexdump (token, sizeof (token), " PCD_RndA+PCD_RndB' ", 0);
memset (ivec, '\0', sizeof (ivec));
mifare_cbc_des (key, token, ivec, MD_SEND);
mifare_cbc_des (key, token+8, ivec, MD_SEND);
hexdump (token, sizeof (token), "d(PCD_RndA+PCD_RndB') ", 0);
uint8_t msg[17];
msg[0] = 0xAF;
memcpy (msg + 1, token, 16);
DESFIRE_TRANSCEIVE (tag, msg, sizeof (msg), status, n);
uint8_t PICC_E_RndA_s[8];
memcpy (PICC_E_RndA_s, status+1, 8);
hexdump (PICC_E_RndA_s, sizeof (PICC_E_RndA_s), "e(PICC_RndA') ", 0);
uint8_t PICC_RndA_s[8];
memcpy (PICC_RndA_s, PICC_E_RndA_s, 8);
memset (ivec, '\0', sizeof (ivec));
mifare_cbc_des (key, PICC_RndA_s, ivec, MD_RECEIVE);
hexdump (PICC_RndA_s, sizeof (PICC_RndA_s), " PICC_RndA' ", 0);
uint8_t PCD_RndA_s[8];
memcpy (PCD_RndA_s, PCD_RndA, 8);
rol8 (PCD_RndA_s);
hexdump (PCD_RndA_s, sizeof (PCD_RndA_s), " PCD_RndA' ", 0);
if (0 != memcmp (PCD_RndA_s, PICC_RndA_s, 8)) {
return -1;
}
return 0;
}