第三章:APDU协议深度解析

APDU协议,说白了就是智能卡和终端设备之间对话的“语法规则”。我刚开始接触JCOP时,觉得这东西不就是几个字节来回传吗?后来踩了不少坑才明白——细节全在协议里。

这一章我们彻底吃透APDU。从命令格式到响应格式,从Case分类到T=0/T=1的底层差异,我会把我在项目中遇到的那些“诡异问题”一并讲清楚。

3.1 APDU命令格式:你发给卡片的每一句话

APDU命令由两部分组成:头部(Header)主体(Body)。头部固定4个字节,主体可选。

字段 长度 说明
CLA 1字节 指令类别,比如00是标准ISO指令,80是私有指令
INS 1字节 指令码,比如A4是SELECT,B0是READ BINARY
P1 1字节 参数1,具体含义由指令定义
P2 1字节 参数2,常与P1配合使用
Lc 0或1或3字节 命令体数据长度(可选)
Data Lc字节 命令体数据(可选)
Le 0或1或2字节 期望响应数据长度(可选)

关键点:CLA和INS的组合决定了卡片要执行什么操作。我见过有人把CLA写成0x80去发标准SELECT指令,结果卡片一直返回6E00(不支持)——其实CLA错了。

举个例子,一个典型的SELECT命令:

00 A4 04 00 08 A0 00 00 00 03 00 00 00 00

这里CLA=00,INS=A4,P1=04(按名称选择),P2=00,Lc=08,后面8字节是AID。Le没写,表示不期望返回数据。

3.2 APDU响应格式:卡片给你的回答

响应格式更简单:数据体(可选)+ 状态字(SW1 SW2)。状态字固定2字节,是判断命令是否成功的唯一标准。

字段 长度 说明
Data 可变 响应数据(可选)
SW1 1字节 状态高位
SW2 1字节 状态低位

常见的状态字:

  • 90 00:成功。看到这个就放心了。
  • 6A 82:文件未找到。我调JCOP时经常遇到,多半是AID写错了。
  • 69 85:安全条件不满足。嗯,权限不够。
  • 6E 00:CLA不支持。指令类别不对。
  • 6D 00:INS不支持。指令码不对。

个人经验:调试时我习惯先打印SW1 SW2,再去看数据。状态字能告诉你80%的问题。我曾经花了一下午查一个数据解析bug,最后发现是卡片返回了69 85——我忘了先验证PIN。

3.3 Case 1-4指令分类:四种对话模式

APDU命令根据是否有数据发送和接收,分为4种Case。理解这个分类,你才能正确构造命令。

Case 命令格式 说明 示例
Case 1 CLA INS P1 P2 无数据发送,无数据接收 GET CHALLENGE(部分实现)
Case 2 CLA INS P1 P2 Le 无数据发送,期望接收数据 READ BINARY
Case 3 CLA INS P1 P2 Lc Data 发送数据,不期望接收 WRITE BINARY(不带返回)
Case 4 CLA INS P1 P2 Lc Data Le 发送数据,期望接收 VERIFY PIN + 返回尝试次数

为什么会这样分?说白了就是通信效率。Case 1最轻量,Case 4最重。我见过有人把Case 2的Le写成0x00,结果卡片返回了256字节——其实0x00在T=1协议里表示期望最多256字节,但在T=0里含义不同。

避坑指南:我曾经在Case 4命令里忘了写Le,结果卡片一直不返回数据。查了半天才发现——JCOP的Java Card实现里,如果Le缺失,卡片会认为你不需要响应数据,直接返回SW1 SW2。

3.4 T=0与T=1协议区别:底层传输的两种哲学

T=0和T=1是ISO 7816-3定义的两种传输协议。它们决定了APDU在物理层怎么传。嗯,这里要注意——它们不是APDU本身,而是APDU的“运输方式”。

特性 T=0(字符协议) T=1(块协议)
传输单位 单个字符(字节) 数据块(可变长度)
错误检测 奇偶校验(每个字节) CRC/LRC(整个块)
重传机制 简单重发字符 块重传 + 序号管理
APDU适配 需要过程字节(如INS=0xC0) 直接映射APDU
典型应用 老式SIM卡、存储卡 Java Card、高端安全芯片

T=0协议:每个字节独立传输,卡片收到一个字节就处理一个。如果卡片需要额外时间,会发一个过程字节(比如0x60表示“我还在忙”)。我调过一款老式SIM卡,用的就是T=0,每次发命令都要手动处理过程字节,很麻烦。

T=1协议:把整个APDU打包成一个块,一次性发送。块有头(NAD、PCB、LEN)、数据体、尾(CRC)。卡片收到完整块后才解析。JCOP默认用T=1,因为Java Card的APDU处理天然适合块传输。

核心差异:T=0是“字节流”,T=1是“报文”。T=0的每个字节都可能改变通信状态,T=1的块边界清晰。你想想看,如果APDU有256字节数据,T=0要来回握手几百次,T=1一次搞定。

我举个例子。在T=0下发一个Case 4命令(有发送有接收),流程是这样的:

  1. 终端发CLA INS P1 P2
  2. 卡片回过程字节(比如0x60)
  3. 终端发Lc
  4. 卡片回过程字节
  5. 终端发Data
  6. 卡片回过程字节(比如0x61 xx,表示有xx字节要返回)
  7. 终端发INS=0xC0(GET RESPONSE)
  8. 卡片返回数据 + SW1 SW2

而在T=1下,一步到位:

终端 -> 卡片: [块头] CLA INS P1 P2 Lc Data Le [CRC]
卡片 -> 终端: [块头] Data SW1 SW2 [CRC]

我的建议:做JCOP开发,优先用T=1。T=0的握手逻辑太容易出错了。我曾经在T=0下忘记处理0x61过程字节,结果卡片一直不返回数据——其实它在等我发GET RESPONSE。

3.5 知识体系总览

下面这张图把APDU协议的核心逻辑串起来了。从命令构造到响应解析,从Case分类到协议选择,每一步都有坑,但也都有规律可循。

APDU协议核心知识体系 APDU命令格式 CLA (1B) | INS (1B) | P1 (1B) | P2 (1B) Lc (0/1/3B) | Data (Lc B) | Le (0/1/2B) 头部固定4字节,主体可选 APDU响应格式 Data (可变长度) SW1 (1B) | SW2 (1B) 状态字决定成败,90 00为成功 Case 1-4 指令分类 Case 1: 无发无收 Case 2: 无发有收 Case 3: 有发无收 Case 4: 有发有收 根据Lc和Le的存在判断,决定通信流程 T=0 字符协议 逐字节传输,奇偶校验 需要过程字节握手 适合老式卡片,调试复杂 T=1 块协议 块传输,CRC校验 直接映射APDU JCOP默认协议,推荐使用 核心:APDU是逻辑协议,T=0/T=1是物理传输协议,两者独立但相互影响

这张图把APDU协议拆成了三层:命令/响应格式是基础,Case分类决定了通信模式,T=0/T=1决定了底层传输方式。我个人习惯把这三层分开理解,调试时逐层排查问题。

最后提醒:别把Case分类和T=0/T=1搞混。Case分类是APDU层面的,T=0/T=1是传输层面的。我见过有人问“Case 4是不是只能用T=1?”——不是的,Case 4在T=0下也能跑,只是要多几个握手步骤。

好了,APDU协议的核心就这些。下一章我们进入实战——用Python写一个APDU调试工具,把今天讲的理论全用上。


公众号:蓝海资料掘金营,微信deep3321