通过 Android USB 主机与智能卡读卡器通信

Posted

技术标签:

【中文标题】通过 Android USB 主机与智能卡读卡器通信【英文标题】:Communicate with smartcard reader through Android USB host 【发布时间】:2017-04-19 22:35:35 【问题描述】:

我正在尝试向智能卡发送命令。我使用Gemalto IDBridge CT30 (PC TWIN reader) 和IDBridge K30 通过USB 连接到android 设备。

我尝试通过 USB 发送 SELECT APDU 命令:

boolean claim = openedConnection.claimInterface(usbInterface, true);
byte[] data = new byte[]
        (byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x0C,
        (byte) 0x07, (byte) 0xA0, (byte) 0x00, (byte) 0x00,
        (byte) 0x01, (byte) 0x18, (byte) 0x45, (byte) 0x4E;

之后我收到了答复:

final int dataTransferred = this.openedConnection.bulkTransfer(endPointOut, data, data.length, TIMEOUT_MS);
if(!(dataTransferred == 0 || dataTransferred == data.length)) 
    throw new Exception("Error durring sending command [" + dataTransferred + " ; " + data.length + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$


final byte[] responseBuffer = new byte[endPointIn.getMaxPacketSize()];
final int dataTransferred = this.openedConnection.bulkTransfer(this.endPointIn, responseBuffer, responseBuffer.length, TIMEOUT_MS);
Console.writeLine("USB Retrieve: " + dataTransferred + " " + responseBuffer.length);
if(dataTransferred >= 0)
    return responseBuffer;

throw new Exception("Error durring receinving response [" + dataTransferred + "]");

答案是

0x00 0x00 0x00 0x00 0x00 0xA0 0x00 0x41 0x03 0x00

但是,根据test project here,我应该得到0x90 0x00的答案。

我做错了什么?有谁能够帮我?我是否使用正确的方法?我没有使用javax.smartcardio 的默认包类。我直接使用 USB 接口类(例如UsbDevice)。

【问题讨论】:

【参考方案1】:

您的阅读器设备通过 USB 接口说出 CCID。您不能简单地通过批量输出端点发送 APDU(智能卡命令)并期望通过批量输入端点接收响应 APDU。相反,您需要实现 CCID 设备类协议(请参阅USB Device Class Specifications)。步骤如下:

    发送 PC_to_RDR_IccPowerOn 命令激活卡。 62 00000000 00 00 00 0000 | | | | | | | | | | | | | \--> 空数据字段 | | | | | \-------> 未使用,设置为 0x0000 | | | | \----------> 电源选择:0x00 表示自动选择 | | | \-------------> 序号(每条命令递增) | | \----------------> 插槽号(您的设备似乎为零) | \-------------------------------------> 数据字段的长度(LSB 在前) \----------------------------> 消息类型:0x62 表示 PC_to_RDR_IccPowerOn 通过 RDR_to_PC_DataBlock 接收 ATR。 80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1 | | | | | | | | | | | | | | | \--> 数据字段:ATR | | | | | | \-----> 级别参数 | | | | | \--------> 错误寄存器(成功时应为零) | | | | \-----------> 状态寄存器(成功时应为零) | | | \--------------> 序号(匹配命令的序号) | | \-----------------> 槽号(与命令的槽号匹配) | \--------------------------> 数据字段的长度(LSB 在前) \-----------------------------> 消息类型:0x80 表示 RDR_to_PC_DataBlock 发送包装到 PC_to_RDR_XfrBlock 命令中的命令 APDU 6F 0C000000 00 01 00 0000 00A4040C07A000000118454E | | | | | | | | | | | | | \--> 数据字段:命令 APDU | | | | | \-------> 级别参数(正常长度的 APDU 为 0x0000) | | | | \----------> 阻塞等待超时 | | | \-------------> 序号(每条命令递增) | | \----------------> 插槽号(您的设备似乎为零) | \-------------------------------------> 数据字段的长度(LSB 在前) \----------------------------> 消息类型:0x6F 表示 PC_to_RDR_XfrBlock 通过 RDR_to_PC_DataBlock 接收响应 APDU。 80 02000000 00 01 00 00 00 9000 | | | | | | | | | | | | | | | \--> 数据域:响应APDU | | | | | | \-----> 级别参数 | | | | | \--------> 错误寄存器(成功时应为零) | | | | \-----------> 状态寄存器(成功时应为零) | | | \--------------> 序号(匹配命令的序号) | | \-----------------> 槽号(与命令的槽号匹配) | \--------------------------> 数据字段的长度(LSB 在前) \-----------------------------> 消息类型:0x80 表示 RDR_to_PC_DataBlock 对每个 APDU 交换重复第 3 步和第 4 步(不要忘记增加序列号)。

由于 ATR 指示 T=1 作为第一个协议,您可能需要将 APDU 包装到 T=1 TPDU 中(取决于阅读器配置)。第一个 APDU 的 I 块看起来像:

00 00 0C 00A4040C07A000000118454E 15 | | | | | | | | | \--> LRC(由于 ATR 中缺少 TC):所有其他字节的 XOR 校验和 | | | \---------------------------> INF: APDU | | \------------------------------------------> LEN:INF 字段的长度 | \---------------------------------> PCB:每隔一个 I 块在 0x00 和 0x40 之间切换 \------------------------------------> NAD:节点寻址

所以您的 PC_to_RDR_XfrBlock 命令如下所示:

6F 10000000 00 01 00 0000 00 00 0C 00A4040C07A000000118454E 15

然后您会收到包裹在 I 块或 R 或​​ S 块中的答案,表明需要进行一些特殊/错误处理。

【讨论】:

发送 IccPowerOn 后 - 我收到:80 18 00 00 00 00 00 00 00 00 3B BF 11 00 81 31 FE 45 45 50 41 00 00 00 00 00 00 00 00 00 00 00 00 F1 并在发送下一个命令 XfrBlock 后,我收到:00 00 00 00 00 00 01 40 03 00。但我无法将结果解析为 ResponseAPDU(因为它是包 javax.smartcardio 的一部分),但结果是字节数组(如上所述)。但结果仍然不同。我还将命令作为字节数组发送,而不是作为 CommandAPDU 类发送。 @MichaelRoland @user997777 查看我的更新答案。显然,您不能使用 Java SmartcardIO 中的 APDU 类,因为这在现有的 Android 上不容易获得,尽管您可以在 USB 类和 Java SmartcardIO 框架之上实现自己的提供程序。无论如何,你需要说 CCID 而不是 USB 上的 APDU。 您一定要阅读 CCID 规范。 感谢您的回答,但可能缺少一些东西。我发送的唯一两个命令是 PC_to_RDR_IccPowerOn ,当 ATR 的正确答案将到来时,在该命令 PC_to_RDR_XfrBlock 之后仍然只返回 10 个字节 (80 00 00 00 00 00 01 40 F4 00) 而不是您的 APDU 响应。我已经尝试了很多实现,只包含这两个步骤,我仍然得到相同的结果。有没有可能,我忘记了什么?一些跨命令等等? @MichaelRoland @user997777 您是否查看了 CCID 规范并检查了错误代码 0xF4 的含义?您是否验证了您的卡确实支持具有这些特定参数的命令(特别是选项 P2=0x0C)?例如,通过将阅读器连接到某些 PC 并运行一些现成的 APDU 交换软件。 @user997777 如果您在此处发布您的解决方案会非常有帮助..【参考方案2】:

您发送的是带有给定 AID 的 SELECT 命令,它很容易可以产生结果。但是,您清楚地表明您对回复不感兴趣,通过

将 P2 设置为“0C” 不提供 LE 字节(假设是基于块的协议,对于 USB 来说肯定是合理的)

因此有人可能会得出结论,您的卡不符合 ISO 7816-4 标准;另一方面,响应也不包含任何看起来像错误 SW1/SW2 状态的内容,您确定要转储响应缓冲区吗?

【讨论】:

这组字节(命令)是通过包 javax.smartcardio 的类在经典的默认 Java 项目(主要问题中的链接)中发送的,而我正尝试使用 Android 接口发送相同的字节使用 UsbDevice 之类的类,但响应完全不同。 @guidot (注意,)OP 正在将 APDU 发送到 USB 端点,因此必须使用 CCID - 请参阅 Michael Roland 的回答(您不能在此处直接发送 APDU)

以上是关于通过 Android USB 主机与智能卡读卡器通信的主要内容,如果未能解决你的问题,请参考以下文章

通过 USB 连接的 APDU

在android平板中如何进行NFC通信,就是通过rfid读卡器连接到平板的USB后,读取的内容显示到平板上

如何通过 WMI 获取系统上的所有智能卡读卡器?

如何访问 Omnikey 3121 智能卡读卡器

Mifare卡安全

智能卡读卡器的电源控制