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

12
我试图向智能卡发送指令。我使用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

然而,根据这里的测试项目,我应该得到0x90 0x00的答案。

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

2个回答

18

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

  1. 发送PC_to_RDR_IccPowerOn命令以激活卡片。
    62 00000000 00 00 00 0000 
    |  |        |  |  |  |    |
    |  |        |  |  |  |    \--> 空数据字段
    |  |        |  |  |  \-------> 未使用,设置为0x0000
    |  |        |  |  \----------> 电源选择:0x00表示自动选择
    |  |        |  \-------------> 序列号(每个命令递增)
    |  |        \----------------> 槽号(对于您的设备似乎为零)
    |  \-------------------------> 数据字段长度(LSB优先)
    \----------------------------> 消息类型:0x62表示PC_to_RDR_IccPowerOn
    
  2. 通过RDR_to_PC_DataBlock接收ATR。
    80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1 
    |  |        |  |  |  |  |  |
    |  |        |  |  |  |  |  \--> 数据字段:ATR
    |  |        |  |  |  |  \-----> 级别参数
    |  |        |  |  |  \--------> 错误寄存器(成功时应为零)
    |  |        |  |  \-----------> 状态寄存器(成功时应为零)
    |  |        |  \--------------> 序列号(与命令的序列号相匹配)
    |  |        \-----------------> 槽号(与命令的槽号相匹配)
    |  \--------------------------> 数据字段长度(LSB优先)
    \-----------------------------> 消息类型:0x80表示RDR_to_PC_DataBlock
    
  3. 将APDU命令包装在PC_to_RDR_XfrBlock命令中发送
    6F 0C000000 00 01 00 0000 00A4040C07A000000118454E
    |  |        |  |  |  |    |
    |  |        |  |  |  |    \--> 数据字段:APDU命令
    |  |        |  |  |  \-------> 级别参数(正常长度APDU的情况下为0x0000)
    |  |        |  |  \----------> 块等待超时
    |  |        |  \-------------> 序列号(每个命令递增)
    |  |        \----------------> 槽号(对于您的设备似乎为零)
    |  \-------------------------> 数据字段长度(LSB优先)
    \----------------------------> 消息类型:0x6F表示PC_to_RDR_XfrBlock
    
  4. 通过RDR_to_PC_DataBlock接收响应APDU。
    80 02000000 00 01 00 00 00 9000 
    |  |        |  |  |  |  |  |
    |  |        |  |  |  |  |  \--> 数据字段:响应APDU
    |  |        |  |  |  |  \-----> 级别参数
    |  |        |  |  |  \--------> 错误寄存器(成功时应为零)
    |  |        |  |  \-----------> 状态寄存器(成功时应为零)
    |  |        |  \--------------> 序列号(与命令的序列号相匹配)
    |  |        \-----------------> 槽号(与命令的槽号相匹配)
    |  \--------------------------> 数据字段长度(LSB优先)
    \-----------------------------> 消息类型:
    由于ATR指示T=1作为第一个协议,因此您可能需要将您的APDU包装成T=1 TPDUs(取决于读卡器配置)。第一个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块。

1
发送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
1
@user997777,请看看我的更新回答。显然你不能使用Java SmartcardIO中的APDU类,因为这在原生Android平台上不容易得到,但你可以在USB类和Java SmartcardIO框架之上实现自己的提供程序。无论如何,你需要通过USB传输CCID而不是APDU。你一定要阅读CCID规范文档。 - Michael Roland
让我们在聊天室里继续这个讨论 - user997777
1
@user997777 如果您在这里发布了您的解决方案,那将非常有帮助。 - shadygoneinsane
我总是从第三步得到答案为2。 - Gabbr Issimo
显示剩余10条评论

0

您发送的是一个SELECT命令,带有给定的AID,这很容易产生结果。然而,您明确指出您不感兴趣回应,通过:

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

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


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

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