4、APDU通信详解

APDU,全称是Application Protocol Data Unit。说白了,就是卡片和读卡器之间对话的语言。我刚开始接触JCOP时,觉得这东西挺唬人,一堆十六进制数。后来发现,搞懂它其实就两件事:命令怎么写,响应怎么读。

这一章,咱们就把APDU的底裤扒干净。从命令结构到响应结构,从T=0到T=1,再到我这些年攒下的调试技巧,一次性讲透。

4.1 命令APDU结构

命令APDU,就是终端发给卡片的消息。它由两部分组成:头部(必选)和体部(可选)。

头部固定4个字节:

  • CLA (Class):指令类别。比如00是标准ISO指令,80是私有指令。我见过有人把CLA写成0x80,结果卡片死活不认——因为卡片只支持00类。
  • INS (Instruction):指令码。比如0xA4是SELECT,0xB0是READ BINARY。
  • P1, P2 (Parameter 1 & 2):参数。具体含义看指令定义。比如SELECT时,P1=0x04表示按文件名选择。

体部最多3个字段:

  • Lc (Length of command data):命令数据的长度。1个字节(短格式)或3个字节(扩展格式)。
  • Data:实际要发送的数据。长度由Lc决定。
  • Le (Length of expected response data):期望响应的最大长度。0x00表示期望最多256字节。

一个典型的SELECT命令:

00 A4 04 00 07 A0 00 00 00 03 00 00 00

分解一下:CLA=00, INS=A4, P1=04, P2=00, Lc=07, Data=7字节AID, Le=00(期望最多256字节响应)。

嗯,这里要注意:Lc和Le不会同时出现。有数据发就带Lc,想收数据就带Le。如果既发又收,那就Lc和Le都带。我见过新手把Lc和Le写反,卡片直接返回6A86(参数错误)。

4.2 响应APDU结构

响应APDU,就是卡片回给终端的话。结构更简单:数据体(可选)+ 状态字(必选)。

状态字(SW1 SW2):

  • 两个字节,表示命令执行结果。
  • 90 00:成功。看到这个就放心了。
  • 6A 82:文件未找到。我调SELECT时经常遇到。
  • 69 85:安全状态不满足。权限不够,先认证吧。
  • 67 00:Lc长度错误。数据长度不对。

数据体:

  • 可选。如果命令有Le,且卡片有数据返回,就放在状态字前面。
  • 长度由命令中的Le决定,但实际返回可能更短。

我的调试习惯:每次收到响应,先看SW1 SW2。如果不是90 00,就别急着看数据。先把状态字查明白,问题就解决了一半。

4.3 T=0与T=1协议区别

T=0和T=1,是ISO 7816-3定义的两种传输协议。说白了,就是数据怎么在线上跑。

特性 T=0(字符传输) T=1(块传输)
传输单位 单个字节 数据块(含头、体、尾)
错误检测 奇偶校验(每个字节) CRC/LRC(整个块)
重传机制 简单重发 块序号+重传请求
典型应用 老式SIM卡、存储卡 Java Card、高端安全芯片
传输效率 低(每个字节都要握手) 高(批量传输)

T=0协议的特点:

  • 每个字节传输后,接收方要回一个ACK(0x90)或NAK(0x60)。
  • 如果收到NAK,发送方重传该字节。
  • 命令APDU的Le字段在T=0下有点特殊:如果Le=0x00,表示期望256字节;如果Le=0x01~0xFF,就是实际期望长度。

T=1协议的特点:

  • 数据被封装成块(Block)。每个块有块头(1字节NAD + 1字节PCB + 1字节LEN)、数据体、和1~2字节CRC。
  • 块序号(0或1)用于检测重复或丢失。
  • 支持链式传输(大块数据分多次发)。

我个人习惯用T=1。为什么?因为T=0的奇偶校验在噪声环境下太脆弱了。我在项目中遇到过,读卡器离卡片稍微远一点,T=0就疯狂重传,效率惨不忍睹。换成T=1后,CRC校验加上块重传,稳得很。

避坑指南:我曾经在T=0协议下,Le字段写0x00想收256字节,结果卡片返回了0字节数据。后来查规范才发现:T=0下Le=0x00表示「我不收数据」,而不是「收256字节」。这个坑,我记了好几年。

4.4 APDU调试技巧

调试APDU,说白了就是跟十六进制打交道。我总结了几个实用技巧,希望能帮你少走弯路。

技巧1:用日志记录每一次交互

我习惯在代码里加一个APDU日志模块。每次发送命令和接收响应,都原样记录下来。格式很简单:

[发送] 00 A4 04 00 07 A0 00 00 00 03 00 00 00
[接收] 6F 1A 84 07 A0 00 00 00 03 00 00 A5 0F 9F 38 0C 9F 66 04 9F 02 06 9F 03 06 90 00

这样一旦出问题,翻日志就能定位。我靠这招解决过不少疑难杂症。

技巧2:善用APDU调试工具

别只用代码调。用工具先验证命令对不对。我推荐几个:

  • GPJ (GlobalPlatform Java Tool):开源,支持脚本,适合批量测试。
  • JCOP Tools:NXP官方工具,调试JCOP卡首选。
  • PC/SC 调试器:Windows下用,能抓取底层APDU。

技巧3:从状态字反推问题

状态字是卡片给你的第一线索。我整理了一个速查表:

SW1 SW2 含义 常见原因
90 00 成功 一切正常
6A 82 文件未找到 AID或文件路径写错了
69 85 安全状态不满足 没认证就操作安全对象
67 00 Lc长度错误 数据长度跟指令不匹配
6D 00 INS不支持 指令码写错了
6E 00 CLA不支持 类别字节不对

技巧4:用脚本自动化测试

手动敲APDU太慢了。我写过一个Python脚本,批量发送命令并验证响应:

import smartcard.System

readers = smartcard.System.readers()
reader = readers[0]
connection = reader.createConnection()
connection.connect()

# 发送SELECT命令
cmd = [0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(cmd)
print(f"SW: {sw1:02X} {sw2:02X}")
print(f"Data: {bytes(data).hex()}")

这个脚本我用了好几年,改改就能适配不同场景。

技巧5:注意时序和重试

卡片不是电脑,处理需要时间。我建议:

  • 命令之间加至少10ms延迟。
  • 如果收到6A 82(文件未找到),别急着放弃。可能是卡片还没准备好,重试一次。
  • 如果收到69 85(安全状态不满足),先检查认证流程。我遇到过认证顺序搞反的情况。

核心总结:APDU调试,说白了就是「发命令、看状态、查数据」三步走。状态字是卡片给你的第一线索,别忽略它。工具用起来,日志记起来,问题就好解决了。

APDU通信知识体系 命令APDU 响应APDU T=0 vs T=1 CLA INS P1 P2 Lc Data Le 数据体 SW1 SW2 字符传输 块传输 APDU调试技巧 日志记录 调试工具 状态字速查 脚本自动化

专注资料整理