Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6.3 通信协议实战:从代码到波形的“变身”

一个场景:你的代码终于调通了,能够向HSM发送命令并获得响应。但当你用逻辑分析仪观察波形时,发现发送的数据和代码里构造的数据不完全一样——多了一些字节,少了另一些字节。为什么?

一个问题浮出水面:数据从代码到HSM芯片,经历了怎样的“变身”?什么是帧格式?什么是APDU?它们之间是什么关系?

一个隐喻:这像寄快递——你写的信(APDU命令)需要装进信封(帧),贴上地址标签(PIB),交给快递员(I2C/SPI司机),才能送达目的地(HSM)。每一层都有自己的格式要求。

通信协议层次

Host与HSM之间的通信采用三层协议结构:

通信协议层次

┌─────────────────────────────────────┐
│  应用层:APDU(应用协议数据单元)      │  ← 业务命令
│  格式:CLA | INS | P1 | P2 | Lc | Data | Le
├─────────────────────────────────────┤
│  数据链路层:帧(Frame)              │  ← 传输容器
│  格式:PIB | LEN | DATA | CRC
├─────────────────────────────────────┤
│  物理层:I2C/SPI                     │  ← 硬件传输
│  I2C:地址 + 读/写 + 数据
│  SPI:片选 + 全双工传输
└─────────────────────────────────────┘

每一层协议都有自己的职责:

  • 应用层APDU:定义业务命令的内容,如“生成密钥”、“验证PIN”
  • 数据链路层帧:封装APDU,添加长度、校验等传输必要信息
  • 物理层I2C/SPI:执行实际的电信号传输

APDU协议详解

APDU(Application Protocol Data Unit)是智能卡通信的标准格式,源自ISO 7816-4规范。HSM沿用了这一格式。

命令APDU结构

命令APDU结构

┌────┬────┬────┬────┬────┬─────────┬────┐
│CLA │INS │ P1 │ P2 │ Lc │  Data   │ Le │
├────┼────┼────┼────┼────┼─────────┼────┤
│ 1  │ 1  │ 1  │ 1  │变长│   变长   │变长│
│字节│字节│字节│字节│    │  字节   │    │
└────┴────┴────┴────┴────┴─────────┴────┘

各字段含义:

字段长度说明
CLA1字节指令类别。0x00表示标准命令,0x80表示厂商自定义命令
INS1字节指令代码。如0xA4表示SELECT,0x84表示GET CHALLENGE
P11字节参数1,为INS提供更详细参数
P21字节参数2,为INS提供更详细参数
Lc变长命令数据长度,表示Data字段的字节数
Data变长命令数据,由Lc指定长度
Le变长期望响应数据的最大长度

响应APDU结构

响应APDU结构

┌─────────┬────┬────┐
│  Data   │SW1 │SW2 │
├─────────┼────┼────┤
│   变长   │ 1  │ 1  │
│  字节   │字节│字节│
└─────────┴────┴────┘

各字段含义:

字段长度说明
Data变长响应数据,由命令APDU中的Le指定最大长度
SW11字节状态字1,与SW2组合表示执行结果
SW21字节状态字2

常见状态字

SW1-SW2含义说明
0x9000成功命令执行成功
0x6982安全条件不满足需要先验证PIN才能执行
0x6A82文件未找到指定的文件不存在
0x6A86参数不正确P1或P2参数无效
0x6D00指令不支持INS代码不被HSM支持

APDU命令示例

获取随机数

发送:00 84 00 00 08

解析:
CLA = 0x00   // 标准命令
INS = 0x84   // GET CHALLENGE(获取随机数)
P1  = 0x00   // 参数1
P2  = 0x00   // 参数2
Le  = 0x08   // 期望8字节随机数

响应:61 65 5A DC E1 61 C5 73 90 00

解析:
Data = 61 65 5A DC E1 61 C5 73   // 8字节随机数
SW1  = 0x90                       // 成功
SW2  = 0x00

选择文件

发送:00 A4 00 00 02 00 06

解析:
CLA = 0x00   // 标准命令
INS = 0xA4   // SELECT FILE(选择文件)
P1  = 0x00   // 参数1
P2  = 0x00   // 参数2
Lc  = 0x02   // 数据长度为2字节
Data = 00 06 // 文件ID = 0x0006

响应:90 00

解析:
Data = 无   // 选择文件不需要返回数据
SW1  = 0x90 // 成功
SW2  = 0x00

帧格式详解

APDU不能直接通过I2C/SPI发送,需要封装成帧。帧格式定义了数据链路层的传输单元。

帧结构

帧结构

┌────┬─────────┬─────────┬────┐
│PIB │   LEN   │  DATA   │CRC │
├────┼─────────┼─────────┼────┤
│ 1  │   2     │  变长   │ 2  │
│字节│  字节   │  字节   │字节│
└────┴─────────┴─────────┴────┘

各字段含义:

字段长度说明
PIB1字节协议控制字节,定义帧类型
LEN2字节数据长度,DATA字段的有效字节数
DATA变长有效数据,即APDU内容
CRC2字节校验码,用于检测传输错误

PIB帧类型

PIB(Protocol Control Byte)最高两位定义帧类型:

PIB高两位类型说明
00xxxxxxI帧信息帧,承载APDU数据
01xxxxxxR帧响应帧,确认接收或请求重发
10xxxxxxS帧状态帧,传输控制信息

帧示例

发送获取随机数命令,完整帧为:

发送帧:20 00 05 00 84 00 00 08 D3 42

解析:
PIB = 0x20       // I帧,序号0
LEN = 0x0005     // 数据长度5字节
DATA = 00 84 00 00 08   // APDU命令
CRC = 0xD342     // 校验码

响应帧:20 00 0A 61 65 5A DC E1 61 C5 73 90 00 XX XX

解析:
PIB = 0x20       // I帧
LEN = 0x000A     // 数据长度10字节(8字节随机数 + 2字节状态)
DATA = 61 65 5A DC E1 61 C5 73 90 00   // APDU响应
CRC = 校验码

帧组包过程

SDK内部的组包流程:

组包流程

用户调用 hsm_get_random()
    ↓
构造APDU:00 84 00 00 08
    ↓
调用 tpdu_pack() 组帧
    ↓
添加 PIB = 0x20
添加 LEN = 0x0005
DATA = APDU内容
计算 CRC
    ↓
完整帧:20 00 05 00 84 00 00 08 D3 42
    ↓
调用 driver_spi_transceive() 发送
    ↓
SPI硬件传输

CRC计算

CRC使用CRC-16算法,确保数据传输的可靠性:

uint16_t calculate_crc(uint8_t *data, uint32_t len) {
    uint16_t crc = 0xFFFF;
    for (uint32_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc >>= 1;
        }
    }
    return crc;
}

I2C vs SPI对比

HSM支持两种物理层协议:I2C和SPI。它们各有特点。

I2C特点

I2C通信模型

Host(主设备)              HSM(从设备)
    │                          │
    │──── SCL(时钟线)───────│
    │                          │
    │──── SDA(数据线)───────│
    │                          │
    │   START → 地址 → 数据 → STOP

优点:

  • 只需要2根线(SCL、SDA)
  • 硬件简单,成本低
  • 支持多从设备共享总线

缺点:

  • 速率较低(标准模式100kHz,快速模式400kHz)
  • 主设备控制时序,从设备被动响应
  • 某些主设备硬件有限制,无法一次接收长帧

SPI特点

SPI通信模型

Host(主设备)              HSM(从设备)
    │                          │
    │──── SCK(时钟线)───────│
    │                          │
    │──── MOSI(主出从入)────│
    │                          │
    │──── MISO(主入从出)────│
    │                          │
    │──── CS(片选线)────────│
    │                          │
    │   全双工同步传输

优点:

  • 全双工传输,效率高
  • 速率可达数MHz
  • 主设备完全控制传输过程
  • 无硬件接收长度限制

缺点:

  • 需要4根线(SCK、MOSI、MISO、CS)
  • 每个从设备需要独立的片选线

实际案例:I2C硬件限制

在一个车载平台上,我们遇到了I2C接收长帧的问题:

问题描述

发送帧:请求获取密钥信息(帧长度519字节)
    ↓
I2C主设备开始接收
    ↓
接收约250字节后,主设备硬件自动STOP
    ↓
主设备再次START
    ↓
从设备(HSM)从头开始重新发送
    ↓
主设备收到的数据:
20 00 08 01 20 00 08 01 20 00 08 01 ...
(数据重复,CRC错误)

原因分析:某些I2C控制器在处理长数据读取时,会将一次事务拆分成多个小事务。每次拆分后释放总线(STOP),再重新获取总线(START)。HSM从设备收到新的START后,会从头开始发送数据,导致接收端数据错乱。

解决方案:切换到SPI协议。SPI没有这种硬件限制,可以一次传输完整帧。

SPI配置示例

设备文件:/dev/spidev1.1
模式:Mode 1(CPOL=0, CPHA=1)
位宽:8位
速率:1MHz

SPI验证代码

#include <linux/spi/spidev.h>

int spi_transfer(int fd, uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len) {
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx_buf,
        .rx_buf = (unsigned long)rx_buf,
        .len = len,
        .delay_usecs = 0,
        .bits_per_word = 8,
        .speed_hz = 1000000,
    };
    return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}

自定义APDU发送

SDK提供了hsm_transceive函数,允许用户发送自定义APDU:

uint8_t in_buf[5] = {0x00, 0x84, 0x00, 0x00, 0x08};  // 获取8字节随机数
uint8_t out_buf[32];
uint32_t out_buf_len;

ret = hsm_transceive(in_buf, 5, out_buf, &out_buf_len);
if (ret == HSM_OK) {
    // out_buf包含响应数据 + SW1/SW2
    // out_buf_len为响应总长度
}

这给了用户最大的灵活性,可以发送SDK API未直接支持的命令。

小结

通信协议像信件投递系统:

  • APDU是信件内容,写着你要HSM做的事
  • 是信封,保护信件并添加邮递必要信息(长度、校验)
  • I2C/SPI是邮递员,负责把信封送到目的地

理解每一层协议的职责,是调试通信问题的关键。当你看到波形分析仪上的数据时,就能一层层解析:这是什么帧?承载什么APDU?命令是什么?响应是什么?

下一节,我们将分享真实踩坑案例——看调试实战经验。

【下集预告】

SDK也会有bug?

段错误怎么定位?类型转换问题怎么发现?

I2C硬件限制怎么解决?

下一节,踩坑与修复。