第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命令格式 • 头部:CLA, INS, P1, P2(4字节) • 主体:Lc, Data, Le(可选) • 短格式:Lc/Le ≤ 255字节 • 扩展格式:Lc/Le ≤ 65535字节 • CLA的b8位:安全消息标志 APDU响应格式 • 数据域(可选) • 状态字:SW1 + SW2(2字节) • 0x9000:成功 • 0x6A82:文件未找到 • 0x6985:条件不满足 传输协议 • T=0:字符传输,逐字节交互 • T=1:块传输,带CRC校验 • 过程字节:0x60/0x61/0x6x • 链式传输:分块传大包 链路层安全 • SCP01:DES加密 + MAC • SCP02:AES加密 + MAC • 显式MAC vs 隐式MAC • 会话密钥:动态生成 APDU是应用层协议,T=0/T=1是传输层,安全通道是保护层

这张图把APDU通信协议分成了三个层次:命令/响应格式、传输协议、链路安全。我个人觉得,理解这三层的关系很重要。你想想看,如果只懂APDU格式,不懂T=0/T=1的传输机制,那调试时遇到过程字节错误就抓瞎了。反过来,如果只懂传输协议,不懂安全通道,那开发出来的产品可能就是个“裸奔”的卡片。

避坑指南:我曾经在一个项目中,卡片支持T=0和T=1两种协议,但终端只实现了T=0。结果卡片在ATR(复位应答)中声明支持T=1,终端直接懵了。后来我们在卡片固件中强制固定为T=0协议,才解决了问题。所以,协议协商一定要在ATR阶段处理好

好了,APDU通信协议就讲到这里。记住一句话:APDU是卡片的“语言”,T=0/T=1是“电话线”,安全通道是“加密通话”。这三者缺一不可,任何一个环节出问题,卡片都没法正常工作。


专注资料整理