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。原因有三:

  1. 支持长数据:虽然单块最大254字节,但可以通过链式块(Chain Bit)连续发送,理论上不限长度。
  2. 错误重传:T=1有明确的NAK机制,接收方发现校验错误可以要求重传。T=0只能靠超时重试,效率低。
  3. 全双工潜力:虽然实际应用中还是半双工居多,但协议本身允许双向同时传。

我的建议:如果你的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 知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以把它当作一个快速索引,遇到问题时回来对照。

ISO 7816 通信协议知识体系 ISO 7816 通信协议知识体系 物理层:触点定义 + 复位应答 (ATR) 传输层:T=0 协议(字符传输) vs T=1 协议(块传输) T=0 协议特点 • 面向字符,一次传1字节 • 半双工,一问一答 • 最大数据长度:256字节 T=1 协议特点 • 面向块,打包传输 • 支持链式块,不限长度 • 有校验和重传机制 应用层:APDU 指令结构 命令APDU:CLA | INS | P1 | P2 | Lc | Data | Le 响应APDU:Data | SW1 | SW2 核心:APDU是“指令信封”,T=0/T=1是“快递方式”

4.7 本章小结

这一章咱们把SE的通信协议拆开了揉碎了讲了一遍。你记住几个关键点就行:

  • T=0:字符传输,简单但效率低,适合小数据量场景
  • T=1:块传输,稳定可靠,适合大数据量场景
  • APDU:命令和响应的标准格式,CLA/INS/P1/P2/Lc/Data/Le各司其职
  • 状态字:SW1/SW2是SE的“表情”,学会看它就能读懂SE的心情