Mifare Desfire Wrapped Mode: How to calculate CMAC?

2.3k Views Asked by At

When using Desfire native wrapped APDUs to communicate with the card, which parts of the command and response must be used to calculate CMAC?

After successful authentication, I have the following session key:

Session Key: 7CCEBF73356F21C9191E87472F9D0EA2

Then when I send a GetKeyVersion command, card returns the following CMAC which I'm trying to verify:

<< 90 64 00 00 01 00 00
>> 00 3376289145DA8C27 9100

I have implemented CMAC algorithm according to "NIST special publication 800-38B" and made sure it is correct. But I don't know which parts of command and response APDUs must be used to calculate CMAC.

I am using TDES, so MAC is 8 bytes.

2

There are 2 best solutions below

0
On

Sry for my English,- its terrible :) but it's not my native language. I'm Russian.

Check first MSB (7 - bit) of array[0] and then shiffting this to the left. And then XOR if MSB 7 bit was == 1; Or save first MSB bit of array[0] and after shiffting put this bit at the end of array[15] at the end (LSB bit).

Just proof it's here: https://www.nxp.com/docs/en/application-note/AN10922.pdf

Try this way:

Zeros <- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

SessionKey <- 00 01 02 03 E3 27 64 0C 0C 0D 0E 0F 5C 5D B9 D5

Data <- 6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

First u have to encrypt 16 bytes (zeros) with SesionKey;

enc_aes_128_ecb(Zeros);

And u get EncryptedData.

EncryptedData <- 3D 08 A2 49 D9 71 58 EA 75 73 18 F2 FA 6A 27 AC

Check bit 7 [MSB - LSB] of EncryptedData[0] == 1? switch i to true;

 bool i = false;
  if (EncryptedData[0] & 0x80){
    i = true;
  }

Then do Shiffting of all EncryptedData to 1 bit <<.

ShiftLeft(EncryptedData,16);

And now, when i == true - XOR the last byte [15] with 0x87

if (i){
    ShiftedEncryptedData[15] ^= 0x87;
  }

7A 11 44 93 B2 E2 B1 D4 EA E6 31 E5 F4 D4 4F 58

Save it as KEY_1.

Try bit 7 [MSB - LSB] of ShiftedEncryptedData[0] == 1?

 i = false;
  if (ShiftedEncryptedData[0] & 0x80){
    i = true;
  }

Then do Shiffting of all ShiftedEncryptedData to 1 bit <<.

ShiftLeft(ShiftedEncryptedData,16);

And now, when i == true - XOR the last byte [15] with 0x87

if (i){
   ShiftedEncryptedData[15] ^= 0x87;
}

F4 22 89 27 65 C5 63 A9 D5 CC 63 CB E9 A8 9E B0

Save it as KEY_2.

Now we take our Data (6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00)

As Michael say's - pad command with 0x80 0x00...

XOR Data with KEY_2 - if command was padded, or KEY_1 if don't. If we have more like 16 bytes (32 for example) u have to XOR just last 16 bytes.

Then encrypt it:

enc_aes_128_ecb(Data);

Now u have a CMAC.

CD C0 52 62 6D F6 60 CA 9B C1 09 FF EF 64 1A E3


Zeros <- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

SessionKey <- 00 01 02 03 E3 27 64 0C 0C 0D 0E 0F 5C 5D B9 D5

Key_1 <- 7A 11 44 93 B2 E2 B1 D4 EA E6 31 E5 F4 D4 4F 58

Key_2 <- F4 22 89 27 65 C5 63 A9 D5 CC 63 CB E9 A8 9E B0

Data <- 6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

CMAC <- CD C0 52 62 6D F6 60 CA 9B C1 09 FF EF 64 1A E3

C/C++ function:

void ShiftLeft(byte *data, byte dataLen){
  for (int n = 0; n < dataLen - 1; n++) {
   data[n] = ((data[n] << 1) | ((data[n+1] >> 7)&0x01));
  }
  data[dataLen - 1] <<= 1;
}   

Have a nice day :)

4
On

I have been looking at the exact same issue for the last few days and I think I can at least give you some pointers. Getting everything 'just so' has taken some time and the documentation from NXP (assuming you have access) is a little difficult to interpret in some cases.

So, as you probably know, you need to calculate the CMAC (and update your init vec) on transmit as well as receive. You need to save the CMAC each time you calculate it as the init vec for the next crypto operation (whether CMAC or encryption etc).

When calculating the CMAC for your example the data to feed into your CMAC algorithm is the INS byte (0x64) and the command data (0x00). Of course this will be padded etc as specified by CMAC. Note, however, that you do not calculate the CMAC across the entire APDU wrapping (i.e. 90 64 00 00 01 00 00) just the INS byte and data payload is used.

On receive you need to take the data (0x00) and the second status byte (also 0x00) and calculate the CMAC over that. It's not important in this example but order is important here. You use the response body (excluding the CMAC) then SW2.

Note that only half of the CMAC is actually sent - CMAC should yield 16 bytes and the card is sending the first 8 bytes.

There were a few other things that held me up including:

  • I was calculating the session key incorrectly - it is worth double checking this if things are not coming out as you'd expect
  • I interpreted the documentation to say that the entire APDU structure is used to calculate the CMAC (hard to read them any other way tbh)

I am still working on calculating the response from a Write Data command correctly. The command succeeds but I can't validate the CMAC. I do know that Write Data is not padded with CMAC padding but just zeros - not yet sure what else I've missed.

Finally, here is a real example from communicating with a card from my logs:

  1. Authentication is complete (AES) and the session key is determined to be F92E48F9A6C34722A90EA29CFA0C3D12; init vec is zeros

  2. I'm going to send the Get Key Version command (as in your example) so I calculate CMAC over 6400 and get 1200551CA7E2F49514A1324B7E3428F1 (which is now my init vec for the next calculation)

  3. Send 90640000010000 to the card and receive 00C929939C467434A8 (status is 9100).

  4. Calculate CMAC over 00 00 and get C929939C467434A8A29AB2C40B977B83 (and update init vec for next calculation)

  5. The first half of our CMAC from step #4 matches the 8 byte received from the card in step #3