如何解密Mifare Desfire EV1发送的第一条消息

时间:2013-12-10 19:01:43

标签: java encryption cryptography aes mifare

有没有人知道如何解密从卡发送的第一条消息?我的意思是在身份验证成功后然后你发送一个命令(例如0x51(GetRealTagUID)。它返回00 + random32bits(总是不同)。我尝试解密它:

        private byte[] decrypt(byte[] raw, byte[] encrypted, byte[] iv)
            throws Exception {
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

用decrypt(sessionKey,response,iv)调用它

IV =全零(16字节)

response =在0x51命令之后的32randombits(刚删除了两个零)

有人告诉我,IV在第一个发送命令(0x51)之后发生了变化。如何生成正确的IV来解密该响应?我认为全零是错误的,因为解密的消息总是不同的,它应该总是与同一张卡相同。

- 编辑 -

应用您的(Michael Roland)指令后,解密的响应仍然只是随机位。这是我的代码(我想我做错了):

            byte[] x = encrypt(sessionKey, iv, iv);

            byte[] rx = rotateBitsLeft(x);

            if ((rx[15] & 0x01) == 0x01)
                rx[15] = (byte) (rx[15] ^ 0x87);

            if ((rx[15] & 0x01) == 0x00)
                rx[15] = (byte) (rx[15] ^ 0x01);

            byte[] crc_k1 = rx;

            byte[] rrx = rotateBitsLeft(rx);

            if ((rrx[15] & 0x01) == 0x01)
                rrx[15] = (byte) (rrx[15] ^ 0x87);

            if ((rrx[15] & 0x01) == 0x00)
                rrx[15] = (byte) (rrx[15] ^ 0x01);

            byte[] crc_k2 = rrx;

            byte[] command = { (byte) 0x51, (byte) 0x80, (byte) 0x00,
                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                    (byte) 0x00 };

            for (int i = 0; i < 16; i++){
                command[i] = (byte) (command[i] ^ crc_k2[i]);
            }

            byte[] iv2 = encrypt(sessionKey, command, iv);

            byte[] RealUID = decrypt(sessionKey, ReadDataParsed, iv2);

            Log.e("RealUID", ByteArrayToHexString(RealUID));

-EDIT3 -

仍然总是返回不同的值。我认为问题可能在于:

byte[] iv2 = encrypt(sessionKey, command, iv);

在创建用于解密响应的新IV时使用什么IV?那里全是零。

2 个答案:

答案 0 :(得分:4)

验证后,IV将重置为全零。在使用AES身份验证时,您必须为每个后续命令计算CMAC(即使CMAC实际上未附加到命令中)。因此,对命令进行CMAC计算将导致正确的IV初始化以解码响应。即该命令的CMAC等于用于解密响应的IV。同样,对于所有其他命令,IV是来自先前加密/ CMAC的最后一个密码块。


<强>更新

如何计算CMAC垫XOR值

  • 使用会话密钥加密一个零块(0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00)(使用零的IV)。 - &GT; x[0..15]
  • 向左旋转x[0..15]一位。 - &GT; rx[0..15]
  • 如果最后一位(rx[15]中的位0)是1:xor rx[15] 0x86
  • rx[0..15]存储为crc_k1[0..15]
  • 向左旋转rx[0..15]一位。 - &GT; rrx[0..15]
  • 如果最后一位(rrx[15]中的位0)是1:xor rrx[15] 0x86
  • rrx[0..15]存储为crc_k2[0..15]

如何计算CMAC

  • 使用0x80 0x00 0x00 ...将命令填充到密码的块大小(AES为16字节)。如果命令长度与块大小的倍数匹配,则不添加填充。
  • 对于您的命令(0x51),这看起来像:0x51 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
  • 如果添加了填充,则使用crc_k2[0..15]填充填充命令的最后16个字节。
  • 如果未添加填充,则使用crc_k1[0..15] xor命令的最后16个字节。
  • 加密(在发送模式,即enc(IV xor datablock),上一个区块的密文是新的IV)结果与会话密钥。
  • 最后一个区块的密文是CMAC和新的IV。

更新2:

如何将位向量向左旋转一位

public void rotateLeft(byte[] data) {
    byte t = (byte)((data[0] >>> 7) & 0x001);
    for (int i = 0; i < (data.length - 1); ++i) {
        data[i] = (byte)(((data[i] << 1) & 0x0FE) | ((data[i + 1] >>> 7) & 0x001));
    }
    data[data.length - 1] = (byte)(((data[data.length - 1] << 1) & 0x0FE) | t);
}

答案 1 :(得分:0)

它不是c / c ++部分,但是进入我的位置,我正在查看C开发人员可能存在的错误。 我不能在这里证明Java的实现。但我认为@Michael Roland的方式并非如此!莫比! Maby我错了!

Sry for my English, - 太可怕了:)但它不是我的母语。我是俄罗斯人。

检查数组[0]的第一个MSB(7位),然后将其向左移动。如果MSB 7位= = 1,则XOR; 或者保存数组[0]的第一个MSB位,并在shiffting之后将该位放在数组[15]的末尾(LSB位)。

请在此证明: https://www.nxp.com/docs/en/application-note/AN10922.pdf

尝试这种方式:

  

Zeros&lt; - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

     

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

     

数据&lt; - 6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

首先,您必须使用SesionKey加密16个字节(零);

enc_aes_128_ecb(Zeros);

你得到EncryptedData。

  

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

检查EncryptedData [0]的第7位[MSB - LSB] == 1?将我切换为真;

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

然后将所有EncryptedData Shiffting为1位&lt;&lt;。

ShiftLeft(EncryptedData,16);

现在,当i == true时 - XOR最后一个字节[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位[MSB - LSB] == 1?

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

然后将所有ShiftedEncryptedData的Shiffting为1位&lt;&lt;。

ShiftLeft(ShiftedEncryptedData,16);

现在,当i == true时 - XOR最后一个字节[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。

现在我们获取数据(6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

正如迈克尔所说 - 用0x80 0x00填充命令......

使用KEY_2的XOR数据 - 如果命令被填充,或者KEY_1如果不填写。 如果我们有更多像16个字节(例如32个),你必须XOR只是最后16个字节。

然后加密它:

enc_aes_128_ecb(Data);

现在你有一个CMAC。

  

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

  

Zeros&lt; - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

     

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

     

Key_1&lt; - 7A 11 44​​ 93 B2 E2 B1 D4 EA E6 31 E5 F4 D4 4F 58

     

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

     

数据&lt; - 6F 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00

     

CMAC&lt; - 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;
}   

度过愉快的一天:)