Mifare Desfire包装模式:如何计算CMAC?

3

在使用Desfire本地封装的APDU与卡通信时,哪些命令和响应的部分必须用于计算CMAC?

成功认证后,我拥有以下会话密钥:

Session Key: 7CCEBF73356F21C9191E87472F9D0EA2

然后,当我发送GetKeyVersion命令时,卡片会返回以下CMAC,我正在尝试进行验证:
<< 90 64 00 00 01 00 00
>> 00 3376289145DA8C27 9100

我已经根据“NIST特刊800-38B”实现了CMAC算法,并确保其正确。但我不知道计算CMAC必须使用哪些命令和响应APDU的部分。
我正在使用TDES,因此MAC为8字节。

嗨,Mohammad,你解决了这个问题吗?我也在面临同样的问题... - macbutch
不,我还没有能够解决这个问题。 - Mohammad Banisaeid
我想我已经弄清楚了(请看我的答案)。我遇到的大问题是我没有正确计算会话密钥。 - macbutch
这是针对AES128的,但逻辑相同。 - Stanislav Granovskiy
2个回答

2
我已经看了几天完全相同的问题,我认为我至少可以给你一些指针。调整所有内容需要一些时间,而NXP的文档(假设您有访问权限)在某些情况下有点难以解释。
所以,正如您可能知道的那样,您需要在发送和接收时计算CMAC(并更新初始化向量)。每次计算CMAC或加密等时,都需要保存计算出的CMAC作为下一个加密操作的初始化向量。
在计算此示例的CMAC时,要馈送到CMAC算法中的数据是INS字节(0x64)和命令数据(0x00)。当然,这将按照CMAC规定进行填充等操作。但是请注意,您不会计算整个APDU包装(即90 64 00 00 01 00 00),只使用INS字节和数据有效负载。
在接收时,您需要获取数据(0x00)和第二个状态字节(也是0x00),并对其进行CMAC计算。在这个示例中,这不重要,但顺序很重要。您使用响应正文(不包括CMAC),然后是SW2。
请注意,实际上只发送了CMAC的一半 - CMAC应产生16个字节,而卡片正在发送前8个字节。
还有一些其他的事情阻碍了我,包括:
  • 我错误地计算了会话密钥-如果事情不如预期,请仔细检查此内容
  • 我将文档解释为整个APDU结构用于计算CMAC(难以以其他方式阅读它们)
我仍在努力正确计算Write Data命令的响应。命令成功执行,但我无法验证CMAC。我知道Write Data没有使用CMAC填充,而只是零 - 还不确定我错过了什么其他内容。
最后,这是来自我的日志与卡通信的实际示例:
  1. 认证已完成(AES),会话密钥确定为F92E48F9A6C34722A90EA29CFA0C3D12;初始向量为零。

  2. 我将发送获取密钥版本的命令(就像您的示例中一样),因此我计算6400上的CMAC并得到1200551CA7E2F49514A1324B7E3428F1(这现在是下一个计算的初始向量)。

  3. 向卡片发送90640000010000并接收00C929939C467434A8(状态为9100)。

  4. 计算00 00上的CMAC并得到C929939C467434A8A29AB2C40B977B83(并更新下一个计算的初始向量)。

  5. 步骤#4中CMAC的前半部分与步骤#3中从卡片接收的8个字节匹配。


我正在使用TDES,所以MAC已经是8个字节了。目前我的问题是,我的MAC和卡有时匹配,有时不匹配! - Mohammad Banisaeid
如果您正在进行TDES,则与您的问题中的CMAC不同。我还没有看过那个,但是如果卡片正在发送MAC,那么我认为它只会发送前4个字节。关于它有时不正确...是的,我也看到了。这有点令人困惑(似乎没有清晰地记录)。一个例子是,即使您已经通过身份验证,如果您发送GetVersion命令,则不需要在发送或接收端更新CMAC。我也发现了一些WriteData的奇怪之处(尽管没有深入挖掘,我似乎无法在WriteData之后正确获取CMAC)。 - macbutch
在使用TDES时,MAC为8个字节。 - Mohammad Banisaeid
2
好的,根据您的问题,您正在谈论CMAC。在计算时它是16个字节,但是卡片只会发送给您前8个字节。如果您处于TDES模式,则MAC为8个字节,但卡片只会发送前4个字节。这些是非常重要的区别。对于TDES的MAC计算与AES的CMAC不同。 - macbutch

2

对不起我的英语很糟糕 :) 但这不是我的母语。我是俄罗斯人。

首先检查数组[0]的最高有效位(MSB)是否为1;之后将其向左移动,然后将其与0x01进行异或; 或者在移位后将数组[0]的第一个MSB位保存下来,然后将此位放置在数组[15]的末尾(LSB位)。

请参考以下链接: https://www.nxp.com/docs/en/application-note/AN10922.pdf

请尝试以下方式:

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

首先需要使用SesionKey加密16个字节(zeros)。

enc_aes_128_ecb(Zeros);

您会得到加密数据。

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

检查加密数据的第一个字节的第7位(从最高位到最低位)是否等于1?如果是,将i开关设置为true。

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

然后将所有加密数据向左移动1位 <<。
ShiftLeft(EncryptedData,16);

现在,当i == true时 - 将最后一个字节[15]与0x87进行异或操作

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

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

请将其保存为KEY_1。

尝试判断ShiftedEncryptedData[0]的第7位[从高到低]是否等于1?

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

然后将所有ShiftedEncryptedData向左移动1位。
ShiftLeft(ShiftedEncryptedData,16);

现在,当i等于true时 - 将最后一个字节[15]与0x87进行异或操作

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

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

将其保存为KEY_2。

现在我们需要处理数据(6F80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

按照Michael的说法,用0x80 0x00填充命令...

使用KEY_2对数据进行异或运算 - 如果命令已被填充,则使用KEY_1。 如果有超过16个字节(例如32个),则只需对最后16个字节进行异或运算。

然后加密它:

enc_aes_128_ecb(Data);

现在你有一个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++函数:

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;
}   

祝您有美好的一天 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接