第4章:APDU通信协议详解
APDU,全称是Application Protocol Data Unit。说白了,就是JavaCard和终端设备之间对话的“语言”。我刚开始接触JavaCard时,总觉得这玩意儿不就是发几个字节嘛,有什么好研究的?直到有一次在项目中,因为APDU格式写错了一个字节,整张卡片直接罢工,调试了整整两天才找到问题。嗯,从那以后,我再也不敢小看这个协议了。
4.1 APDU命令格式
APDU命令分为两种:短格式和扩展格式。咱们先看短格式,这是最常用的。
一个标准的APDU命令由两部分组成:头部(Header)和主体(Body)。头部固定4个字节,主体是可选的。
| 字节 | 名称 | 说明 |
|---|---|---|
| CLA | 类别字节 | 指示命令类别,比如0x00表示ISO标准命令 |
| INS | 指令字节 | 具体操作,比如0xA4表示SELECT,0xB0表示READ BINARY |
| P1 | 参数1 | 指令的辅助参数 |
| P2 | 参数2 | 指令的辅助参数 |
| Lc | 数据长度 | 命令中携带的数据字节数(可选) |
| Data | 数据域 | 实际传输的数据(可选) |
| Le | 期望响应长度 | 期望卡片返回的数据字节数(可选) |
举个例子,SELECT命令通常长这样:
00 A4 04 00 07 A0 00 00 00 62 03 01 00
| | | | | |-- 7字节的AID数据 --|
| | | | | Lc = 0x07
| | | | P2 = 0x00
| | | P1 = 0x04 (按名称选择)
| | INS = 0xA4 (SELECT)
| CLA = 0x00
重要提醒:CLA字节的最高位(b8)用于指示安全消息。如果b8=1,表示这条命令使用了安全通道。我在项目中见过有人把这个位搞反了,结果卡片一直返回0x6E00(CLA不支持),排查了半天才发现是这里的问题。
4.2 APDU响应格式
卡片处理完命令后,会返回一个响应。响应格式比命令简单多了:
| 字段 | 说明 |
|---|---|
| Data(可选) | 返回的数据,长度由Le决定 |
| SW1 | 状态字节1 |
| SW2 | 状态字节2 |
SW1和SW2合起来叫状态字(Status Word)。常见的状态字有:
- 0x9000:操作成功。这是最想看到的。
- 0x6A82:文件未找到。你想想看,SELECT一个不存在的AID,就会返回这个。
- 0x6985:条件不满足。比如安全状态不对,没验证PIN就想操作。
- 0x6700:Lc或Le长度错误。这个我踩过坑,后面细说。
个人经验:我习惯在调试时先打印SW1/SW2,再去看数据。因为很多时候,问题就出在状态字上。比如0x6A86(参数P1/P2不正确),一看就知道是参数传错了。
4.3 T=0与T=1协议
APDU是应用层的协议,而T=0和T=1是传输层的协议。说白了,就是数据怎么在物理线路上传输。
T=0协议(字符传输协议)
T=0是面向字符的协议。每个字节单独传输,带奇偶校验。它的特点是:
- 一次只能传一个字节
- 有奇偶校验位,但纠错能力有限
- 适合低速、简单的场景
我记得早期的一些SIM卡用的就是T=0协议。它的流程是这样的:
终端发送: CLA INS P1 P2
卡片响应: ACK (0x60) 或 NAK (0x6x)
终端发送: Lc (如果有数据)
卡片响应: ACK/NAK
终端发送: Data (如果有)
卡片响应: ACK/NAK
终端发送: Le (如果需要响应数据)
卡片响应: 数据 + SW1 SW2
注意:T=0协议中,卡片可以通过过程字节(Procedure Byte)来控制数据流向。比如0x60表示继续,0x61+xx表示有xx字节数据要返回。我曾经遇到过一个坑:卡片返回0x61FF,但终端只读了0xFF字节,结果剩下的数据就丢了。所以一定要处理好过程字节。
T=1协议(块传输协议)
T=1是面向块的协议。它把数据打包成块(Block),每个块有完整的校验。它的优势很明显:
- 支持更长的数据包
- 有CRC校验,可靠性更高
- 支持链式传输(分块传输大块数据)
T=1的块结构如下:
| 节点地址(NAD) | 协议控制字节(PCB) | 长度(LEN) | 应用数据(INF) | 校验码(EDC) |
| 1字节 | 1字节 | 1字节 | 0-254字节 | 1-2字节 |
PCB字节决定了这个块是命令块、响应块还是管理块。我个人觉得T=1比T=0好用多了,尤其是在需要传输大量数据的时候。比如加载一个几十KB的Applet,用T=1就比T=0快得多。
4.4 链路层安全
链路层安全,说白了就是保证APDU在传输过程中不被篡改、不被窃听。JavaCard支持两种安全通道协议:SCP01和SCP02。
SCP01(安全通道协议01)
SCP01是早期的安全协议,现在用得少了。它的特点是:
- 使用单重DES或三重DES加密
- MAC(消息认证码)用于完整性校验
- 加密和MAC使用不同的密钥
SCP02(安全通道协议02)
SCP02是目前的主流。它比SCP01更灵活:
- 支持AES加密(更安全)
- 支持多种密钥长度(16/24/32字节)
- 有显式MAC和隐式MAC两种模式
SCP02的APDU命令在发送前会经过以下处理:
原始命令: 00 A4 04 00 07 A0 00 00 00 62 03 01 00
1. 添加MAC: 在命令末尾追加4字节或8字节的MAC
2. 加密数据: 如果数据域需要加密,用会话密钥加密
3. 调整CLA: 将CLA的b8位置1,表示安全消息
最终命令: 0C A4 04 00 0B A0 00 00 00 62 03 01 00 [MAC]
关键点:安全通道建立后,所有后续的APDU命令都必须经过安全处理。如果漏掉一个,卡片会返回0x6982(安全状态不满足)。我刚开始做安全通道时,就犯过这个错——建立通道后发了一条明文命令,结果卡片直接罢工了。
4.5 知识体系总览
下面这张图总结了APDU通信协议的核心知识结构:
这张图把APDU通信协议分成了三个层次:命令/响应格式、传输协议、链路安全。我个人觉得,理解这三层的关系很重要。你想想看,如果只懂APDU格式,不懂T=0/T=1的传输机制,那调试时遇到过程字节错误就抓瞎了。反过来,如果只懂传输协议,不懂安全通道,那开发出来的产品可能就是个“裸奔”的卡片。
避坑指南:我曾经在一个项目中,卡片支持T=0和T=1两种协议,但终端只实现了T=0。结果卡片在ATR(复位应答)中声明支持T=1,终端直接懵了。后来我们在卡片固件中强制固定为T=0协议,才解决了问题。所以,协议协商一定要在ATR阶段处理好。
好了,APDU通信协议就讲到这里。记住一句话:APDU是卡片的“语言”,T=0/T=1是“电话线”,安全通道是“加密通话”。这三者缺一不可,任何一个环节出问题,卡片都没法正常工作。