使用 ISO 7816-4 APDU 的 DESFire 卡中的外部身份验证

Posted

技术标签:

【中文标题】使用 ISO 7816-4 APDU 的 DESFire 卡中的外部身份验证【英文标题】:External authentication in DESFire card with ISO 7816-4 APDUs 【发布时间】:2015-12-22 22:27:30 【问题描述】:

我尝试使用 ISO 7816-4 APDU 对 DESFire 卡(居民卡)进行身份验证。但它总是失败。我有什么想念的吗?

>>> 00 84 00 00 00(挑战请求 - 5 个字节)

15 29 84 E3 6A AA A6 B7 90 00(挑战响应 10 个字节 - OK)

>>> 00 82 00 00 10 B5 02 0B 80 4F 95 CB E7 8C A6 4D E9 C1 B1 23 A7 00(外部认证请求 - 22 字节)

67 00(外部认证响应 - 检查错误:长度错误)

代码:

// STEP Authentication
// send initial authentication request
byte[] reqRnbEnc = new byte[]
        (byte) 0x00,
        (byte) 0x84,
        (byte) 0x00, (byte) 0x00,
        (byte) 0x00;

// get encrypted RndB
byte[] resRnbEnc = _isoDep.transceive(reqRnbEnc);
_responseTextView.append(String.format("reqRnbEnc: %s length:%d\n", BytesToHexStr(reqRnbEnc), reqRnbEnc.length));
_responseTextView.append(String.format("resRnbEnc: %s length:%d\n", BytesToHexStr(resRnbEnc), resRnbEnc.length));

// remove 2 last characters
byte[] resRnbEncT = new byte[8];
System.arraycopy(resRnbEnc, 0, resRnbEncT, 0, 8);
_responseTextView.append(String.format("-resRnbEncT: %s length:%d\n", BytesToHexStr(resRnbEncT), resRnbEncT.length));

// decrypt RndB
byte[] resRnbDec = MyDES.decrypt(resRnbEncT);
_responseTextView.append(String.format("-resRnbDec: %s length:%d\n", BytesToHexStr(resRnbDec), resRnbDec.length));

// generate RndA
byte[] Rna = new byte[8];
new SecureRandom().nextBytes(Rna);
_responseTextView.append(String.format("-Rna: %s length:%d\n", BytesToHexStr(Rna), Rna.length));

// plain = concate RndA with resRnbDec
byte[] plain = new byte[16];
System.arraycopy(Rna, 0, plain, 0, 8);
System.arraycopy(resRnbDec, 0, plain, 8, 8);
_responseTextView.append(String.format("-plain: %s length:%d\n", BytesToHexStr(plain), plain.length));

//  cipher = encrypt plain
byte[] cipher = MyDES.encrypt(plain);
_responseTextView.append(String.format("-cipher: %s length:%d\n", BytesToHexStr(cipher), cipher.length));

// send cipher request
byte[] reqCipher = new byte[22];
reqCipher[0] = (byte) 0x00;
reqCipher[1] = (byte) 0x82;
reqCipher[2] = (byte) 0x00;
reqCipher[3] = (byte) 0x00;
reqCipher[4] = (byte) cipher.length;
System.arraycopy(cipher, 0, reqCipher, 5, cipher.length);
reqCipher[21] = (byte) 0x00;

// get response
byte[] resCipher = _isoDep.transceive(reqCipher);
_responseTextView.append(String.format("reqCipher: %s length:%d\n", BytesToHexStr(reqCipher), reqCipher.length));
_responseTextView.append(String.format("resCipher: %s length:%d\n", BytesToHexStr(resCipher), resCipher.length));

加密货币:

public class MyDES 
private static String ENCRYPTION_KEY_TYPE = "DESede";
private static String ENCRYPTION_ALGORITHM = "DESede/CBC/NoPadding";

private static byte[] key = new byte[]
        (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, (byte) 0x00, (byte) 0x00;

private static byte[] iv = new byte[]
        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00;

public static byte[] encrypt(byte[] plainText) 
    try 
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKey secretKey = new SecretKeySpec(key, ENCRYPTION_KEY_TYPE);
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
        byte[] encrypted = cipher.doFinal(plainText);
        return encrypted;
     catch (InvalidKeyException e) 
        e.printStackTrace();
     catch (InvalidAlgorithmParameterException e) 
        e.printStackTrace();
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
     catch (NoSuchPaddingException e) 
        e.printStackTrace();
     catch (BadPaddingException e) 
        e.printStackTrace();
     catch (IllegalBlockSizeException e) 
        e.printStackTrace();
    
    return null;


public static byte[] decrypt(byte[] cipherText) 
    try 
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKey secretKey = new SecretKeySpec(key, ENCRYPTION_KEY_TYPE);
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
        byte[] decrypted = cipher.doFinal(cipherText);
        return decrypted;
     catch (InvalidKeyException e) 
        e.printStackTrace();
     catch (InvalidAlgorithmParameterException e) 
        e.printStackTrace();
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
     catch (NoSuchPaddingException e) 
        e.printStackTrace();
     catch (BadPaddingException e) 
        e.printStackTrace();
     catch (IllegalBlockSizeException e) 
        e.printStackTrace();
    
    return null;

【问题讨论】:

【参考方案1】:

外部身份验证命令不得包含 Le 字段。当您包含 Le 字段(最后一个字节编码为 0x00)时,您会收到错误的长度错误。所以你的命令应该是这样的:

00 82 00 00 10 B5 02 0B 80 4F 95 CB E7 8C A6 4D E9 C1 B1 23 A7

在代码中:

byte[] reqCipher = new byte[5 + cipher.length];
reqCipher[0] = (byte) 0x00;
reqCipher[1] = (byte) 0x82;
reqCipher[2] = (byte) 0x00;
reqCipher[3] = (byte) 0x00;
reqCipher[4] = (byte) (cipher.length & 0x0ff);
System.arraycopy(cipher, 0, reqCipher, 5, cipher.length);

还要确保在开始使用 PICC 主密钥 (P2=0) 进行身份验证之前选择了主文件(主应用程序)。特别是因为 android 可能先前选择了另一个应用程序。

【讨论】:

谢谢先生。如何选择 PICC 主密钥(p2=0)?我不明白,我是nfc环境的新手。 P2 是您写入reqCipher[3] 的内容。您当前使用 0x00,因此使用 PICC 主密钥。 在使用 00 A4 04 00 07 D2 76 00 00 85 01 00 进行身份验证之前,我选择了主应用程序。我收到错误 6B00 我在身份验证过程之前选择了主文件,使用 00 A4 00 00 02 3F 00。我收到错误 6982 再一次,如果您的卡与此处相同 (***.com/q/34382363/2425802),则您的卡不是 DESFire。

以上是关于使用 ISO 7816-4 APDU 的 DESFire 卡中的外部身份验证的主要内容,如果未能解决你的问题,请参考以下文章

是否可以使用 Android 设备模拟 felica 卡?

通过 CCID 的 ISO/IEC 7816 命令

选择接触式智能卡的MF

智能卡通信标准

ISO 14443 A 型卡使用 Android 读/写

如何在 DESfire Ev1 卡上使用 ISO7816 选择命令?