Android:使用 NXP MiFare Ultralight C 进行身份验证

Posted

技术标签:

【中文标题】Android:使用 NXP MiFare Ultralight C 进行身份验证【英文标题】:Android: Authenticating with NXP MiFare Ultralight C 【发布时间】:2013-10-17 22:41:37 【问题描述】:

一个多星期以来,我一直在尝试使用 Mifare Ultralight C 对 android 手机进行身份验证。我已经确认我可以写入标签(通过写入不安全的内存页面,然后阅读我写的内容)。我还可以写入关键页 (44-47) 并为所有 16 个关键字节写入 0x00。

当我尝试进行身份验证时,以下是一次交换期间涉及的数据示例 - 它来自我的应用程序编写的日志。谁能告诉我我做错了什么?我AM保密并可以访问完整数据表。请注意,下面的十六进制字符串显然是正在发送和接收的数据的人类可读版本,在代码中由字节数组组成。

发送验证命令

Received rndB: 8A5735694D9D7542

Key: 00000000000000000000000000000000

IV: 0000000000000000

Decrypted rndB: EF340C62E1B866D4

rndB': 340C62E1B866D4EF

rndA: 6E262630E299F94F

rndA+rndB': 6E262630E299F94F340C62E1B866D4EF

Key: 00000000000000000000000000000000

IV: 8A5735694D9D7542

ek(RndA+rndB'): E36C6C46FAAC60BA45DDF5F5A0802C79

发送0xAF + E36C6C46FAAC60BA45DDF5F5A0802C79 后,我立即失去了与标签的连接。我浏览了数据表并阅读了我可以在这里找到的每一篇文章。我还查看了 libfreefare 代码,老实说,我无法弄清楚我做错了什么。

NXP 技术支持完全没有响应。

有什么想法吗?我很茫然。

【问题讨论】:

我发现恩智浦应用笔记 AN0945 中的示例对调试我自己的代码很有指导意义。 太好了,谢谢!我错过了,因为它是一个 DESFire 文档,我没有注意 DocStore 中的那个文件夹。我刚刚下载,它看起来很有希望。再次感谢 - 我非常感谢您的意见。 NFCGuy - 您(或您认识的任何人)是否使用 Android 设备通过 Ultralight-C 成功完成了端到端身份验证? 是的,当然可以。参见例如 this comment 和 this app 我可以确认 NXP 应用确实尝试执行身份验证(使用全零密钥和示例卡中使用的密钥:“BREAKMEIFYOUCAN!”)。我刚刚检查了你上面的计算,它们都是正确的!所以我的第一个猜测是您测试的卡配置了不同的密钥。另一种选择是单步执行代码,这可能会导致 Android 设备对中间的标签进行存在性检查,从而干扰身份验证协议。 【参考方案1】:

下面是执行 Ultralight-C 身份验证的示例 java 代码,如 MF0ICU2 / MIFARE Ultralight C - Contactless ticket IC document(第 7.5.5 章 -- 3DES 身份验证,第 15 页)中所述:

public void authenticate(byte[] key) throws CardException 
    System.out.println("AUTHENTICATE");
    byte[] encRndB = transmitRaw(new byte[]  0x1A, 0x00 );
    if((encRndB.length!=9)||(encRndB[0]!=AF)) 
        throw new RuntimeException("Invalid response!");
    
    encRndB=Arrays.copyOfRange(encRndB, 1, 9);
    System.out.println(" - EncRndB: " + toHex(encRndB));
    byte[] rndB = desDecrypt(key, encRndB);
    System.out.println(" - RndB: " + toHex(rndB));
    byte[] rndBrot = rotateLeft(rndB);
    System.out.println(" - RndBrot: " + toHex(rndBrot));
    byte[] rndA = new byte[8];
    generateRandom(rndA);
    System.out.println(" - RndA: " + toHex(rndA));
    byte[] encRndArotPrime = transmitRaw(ArrayUtils.addAll(new byte[] AF, desEncrypt(key, ArrayUtils.addAll(rndA, rndBrot))));
    if((encRndArotPrime.length!=9)||(encRndArotPrime[0]!=0x00)) 
        throw new RuntimeException("Invalid response!");
    
    encRndArotPrime=Arrays.copyOfRange(encRndArotPrime, 1, 9);
    System.out.println(" - EncRndArot': " + toHex(encRndArotPrime));
    byte[] rndArotPrime = desDecrypt(key, encRndArotPrime);
    System.out.println(" - RndArot': " + toHex(rndArotPrime));
    if(!Arrays.equals(rotateLeft(rndA), rndArotPrime)) 
        throw new RuntimeException("Card authentication failed");
    


protected static SecureRandom rnd = new SecureRandom();
protected static void generateRandom(byte[] rndA) 
    rnd.nextBytes(rndA);


protected byte[] desEncrypt(byte[] key, byte[] data) 
    return performDes(Cipher.ENCRYPT_MODE, key, data);

protected byte[] desDecrypt(byte[] key, byte[] data) 
    return performDes(Cipher.DECRYPT_MODE, key, data);

private byte[] iv = new byte[8];
protected byte[] performDes(int opMode, byte[] key, byte[] data) 
    try 
        Cipher des = Cipher.getInstance("DESede/CBC/NoPadding");
        SecretKeyFactory desKeyFactory = SecretKeyFactory.getInstance("DESede");
        Key desKey = desKeyFactory.generateSecret(new DESedeKeySpec(ArrayUtils.addAll(key, Arrays.copyOf(key, 8))));
        des.init(opMode, desKey, new IvParameterSpec(iv));
        byte[] ret = des.doFinal(data);
        if(opMode==Cipher.ENCRYPT_MODE) 
            iv=Arrays.copyOfRange(ret, ret.length-8, ret.length);
         else 
            iv=Arrays.copyOfRange(data, data.length-8, data.length);
        
        return ret;
     catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) 
        throw new RuntimeException(e);
    


protected static byte[] rotateLeft(byte[] in) 
    return ArrayUtils.add(Arrays.copyOfRange(in, 1, 8), in[0]);

注意:此代码使用Apache Commons Lang。

【讨论】:

以上是关于Android:使用 NXP MiFare Ultralight C 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Proxmark3 小白入门 复制 NXP MIFARE CLASSIC 1k 卡

Mifare卡版获取方法

NXP 公司的 RFID 卡

Android MIFARE NFCA源码解析

未检测到写入 mifare 1k 卡的 Ndef 数据

如何使用Nexus 5获取Mifare Ultralight 16位UID读数