4、通信协议基础:ISO 7816 T=0/T=1协议、APDU指令结构
好,咱们进入第四章。这一章讲的是SE的“语言”——通信协议。你想想看,一个安全元件被焊在主板上,或者塞进SIM卡槽里,它怎么跟外面的世界对话?靠的就是ISO 7816标准定义的这套规矩。
我个人习惯把ISO 7816分成两部分来看:物理层怎么握手,和应用层怎么传数据。物理层就是那几个触点的电压、时序,咱们做上层开发的一般不用太操心。真正要啃的,是T=0和T=1这两种传输协议,以及APDU这个指令结构。
核心要点:APDU是SE的“指令信封”,T=0/T=1是“快递方式”。你写好指令,选对快递,SE才能正确响应。
4.1 从物理连接到逻辑通道
先简单过一下物理层。ISO 7816-3规定了接口触点的定义:VCC供电、GND地、RST复位、CLK时钟,还有一根I/O数据线。SE上电后,会先走一个复位应答(ATR)流程,告诉主机“我是谁、我能干什么”。
ATR里会声明它支持T=0还是T=1,或者两者都支持。我建议你在选型时,优先看SE是否支持T=1。为什么?后面会讲。
4.2 T=0协议:字符传输,半双工
T=0是面向字符的协议。说白了,它一次只传一个字节,而且主机和SE之间是严格交替说话的——你问一句,我答一句,不能抢话。
它的流程大概是:
- 主机发送5字节的指令头(CLA, INS, P1, P2, P3)
- SE收到后,根据P3的值决定接下来是收数据还是发数据
- 然后SE返回一个过程字节(Procedure Byte),告诉主机下一步动作
- 最后SE返回两个字节的状态字(SW1, SW2)表示结果
这里有个坑。T=0协议里,数据长度P3的编码有点绕:
- 如果P3=0x00,表示后续要传256字节
- 如果P3在0x01~0xFF之间,表示实际长度就是P3的值
我曾经在一个项目中,因为P3=0x00的理解偏差,导致读证书时总是少读一个字节。排查了半天才发现,是协议栈把0x00当成了“不传数据”来处理。嗯,这里要注意。
避坑指南:T=0协议没有显式的长度字段,数据长度全靠P3一个字节硬编码。这意味着单次传输最大256字节。如果你要传大块数据(比如证书链),必须拆成多个APDU分片发送。我曾经见过有人试图用T=0传4KB的密钥,结果SE直接返回6E 00(不支持),因为指令长度超了。
4.3 T=1协议:块传输,全双工
T=1是面向块的协议。它把数据打包成“块”(Block),每个块都有完整的头部、信息字段和校验码。主机和SE可以连续发送多个块,不需要一问一答那么死板。
T=1的块结构长这样:
| 字段 | 长度 | 说明 |
|---|---|---|
| 节点地址(NAD) | 1字节 | 源地址和目标地址,一般用0x00 |
| 协议控制字节(PCB) | 1字节 | 标识块类型:I块(信息块)、R块(接收准备块)、S块(管理块) |
| 长度(LEN) | 1字节 | 信息字段的长度,最大254字节 |
| 信息字段(INF) | 0~254字节 | 实际传输的APDU数据 |
| 校验码(EDC) | 1或2字节 | LRC或CRC校验 |
我个人更偏爱T=1。原因有三:
- 支持长数据:虽然单块最大254字节,但可以通过链式块(Chain Bit)连续发送,理论上不限长度。
- 错误重传:T=1有明确的NAK机制,接收方发现校验错误可以要求重传。T=0只能靠超时重试,效率低。
- 全双工潜力:虽然实际应用中还是半双工居多,但协议本身允许双向同时传。
我的建议:如果你的SE同时支持T=0和T=1,优先选T=1。尤其是做eSIM、物联网这类需要频繁传输大数据的场景,T=1的稳定性和效率明显更好。我做过一个车规级SE项目,用T=0时OTA升级经常超时,换成T=1后一次过。
4.4 APDU指令结构:SE的“指令信封”
不管底层是T=0还是T=1,上层应用看到的都是APDU(Application Protocol Data Unit)。APDU分两种:命令APDU(主机发给SE)和响应APDU(SE返回给主机)。
4.4.1 命令APDU结构
命令APDU的格式是固定的:
CLA | INS | P1 | P2 | Lc | Data | Le
每个字段的含义:
| 字段 | 长度 | 说明 |
|---|---|---|
| CLA | 1字节 | 指令类别。0x00表示ISO标准,0x80~0x9F留给厂商自定义 |
| INS | 1字节 | 指令码。比如0xA4是SELECT,0xB0是READ BINARY |
| P1, P2 | 各1字节 | 参数。具体含义由INS决定,比如P1可以指定文件ID的高字节 |
| Lc | 0, 1或3字节 | 后续数据长度。0字节表示无数据,1字节表示0~255,3字节表示0~65535 |
| Data | Lc字节 | 实际要发送的数据 |
| Le | 0, 1, 2或3字节 | 期望响应的最大长度。0字节表示不期望响应,1字节表示期望0~255字节,3字节表示期望0~65535字节 |
这里有个容易混淆的地方:Lc和Le可以同时出现,也可以只出现一个,甚至都不出现。具体规则是:
- 只有Lc:向SE写数据,不期望返回数据(比如写密钥)
- 只有Le:不向SE发数据,只期望读数据(比如读证书)
- Lc和Le都有:先发数据给SE,再等SE返回结果(比如验证PIN码)
- 都没有:纯命令,无数据交互(比如复位)
实战经验:我见过很多新手在构造APDU时,把Lc和Le搞反。比如想读一个文件,却把Le写成了Lc,结果SE一直等数据,主机一直等响应,两边死锁。记住:Lc是“我发给你多少”,Le是“你发给我多少”。
4.4.2 响应APDU结构
SE处理完命令后,返回的响应APDU很简单:
Data | SW1 | SW2
- Data:可选,长度由命令APDU的Le决定
- SW1, SW2:状态字,各1字节。SW1=0x90且SW2=0x00表示成功。其他值表示各种错误或状态
常见的状态字:
| SW1 | SW2 | 含义 |
|---|---|---|
| 0x90 | 0x00 | 正常处理完成 |
| 0x61 | 0xXX | 还有XX字节数据可读,需要用GET RESPONSE取回 |
| 0x6E | 0x00 | 不支持的INS |
| 0x69 | 0x85 | 安全条件不满足(比如没验证PIN) |
| 0x69 | 0x82 | 安全状态不满足(比如权限不够) |
| 0x6A | 0x82 | 文件未找到 |
| 0x67 | 0x00 | Lc长度错误 |
避坑指南:当SE返回0x61 XX时,表示有数据没传完。你必须发一个GET RESPONSE指令(INS=0xC0)去取剩下的数据。我曾经在一个项目中,SE返回0x61 0x20,我忘了发GET RESPONSE,直接发了下一条指令,结果SE返回0x69 0x00(未知错误)。后来查规范才发现,0x61是“我还有话没说完”的意思。
4.5 一个完整的APDU交互示例
咱们看一个实际例子:用SELECT指令选择一个文件。
命令APDU:
CLA = 0x00
INS = 0xA4
P1 = 0x00 // 通过文件ID选择
P2 = 0x0C // 第一次选择,不返回FCI
Lc = 0x02 // 后面有2字节数据
Data= 0x3F 0x00 // 选择主文件MF
Le = 0x00 // 不期望返回数据
实际发送的字节流:00 A4 00 0C 02 3F 00 00
响应APDU:
SW1 = 0x90
SW2 = 0x00
表示选择成功。
如果我想选择后返回FCI(文件控制信息),就把P2改成0x00,Le改成期望长度:
00 A4 00 00 02 3F 00 10
SE会返回:
6F 0E 84 02 3F 00 A5 0A 88 01 01 8A 01 05 90 00
其中6F 0E ... 8A 01 05是FCI数据,90 00是状态字。
4.6 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以把它当作一个快速索引,遇到问题时回来对照。
4.7 本章小结
这一章咱们把SE的通信协议拆开了揉碎了讲了一遍。你记住几个关键点就行:
- T=0:字符传输,简单但效率低,适合小数据量场景
- T=1:块传输,稳定可靠,适合大数据量场景
- APDU:命令和响应的标准格式,CLA/INS/P1/P2/Lc/Data/Le各司其职
- 状态字:SW1/SW2是SE的“表情”,学会看它就能读懂SE的心情