开源之夏02:SDF接口规范与软件实现设计

进入第三周后,核心工作转向深入理解 SDF 标准规范,并设计一套纯软件模拟实现的架构方案。

理解 SDF 规范:从文档到代码

密标委 发布的 密码设备应用接口规范 (GM/T 0018) 定义了密码卡设备的标准调用接口。通过将规范文档与 GmSSL 源码对照阅读,可以清晰地看到代码结构与规范的一一对应关系。

sdf_int.h 与规范的关系

GmSSL 的 sdf_int.h 定义了实现 SDF 接口所需的所有数据结构和函数接口。关键设计点:

函数指针表(SDF_METHOD) 是整套架构的核心。每种密码设备操作(OpenDevice、Encrypt、Sign 等)都定义为函数指针,运行时通过填充函数指针表来选择具体实现:

1
2
3
4
5
6
typedef struct sdf_method_st {
SDF_OpenDevice_fn OpenDevice;
SDF_CloseDevice_fn CloseDevice;
SDF_OpenSession_fn OpenSession;
// ... 69+ 个函数指针
} SDF_METHOD;

为什么用函数指针?因为 C 语言没有虚函数和接口——这就是 C 语言中手工实现的”虚函数表(vtable)”。输入参数用一级指针(只读),返回参数用二级指针(需要修改调用者的指针指向),这是 C 语言中表示”输出参数”的标准惯用法。

单包与多包加密

SDF 规范中区分单包(Single-Part)和多包(Multi-Part)操作,这是一个需要理解的重要概念:

  • 单包:数据以单个完整块的形式加密/解密。适用于小数据块(一个消息、一个文件)。每次操作独立,不依赖前后文。
  • 多包:数据被分成多个部分分批处理。每个部分可能依赖前一个部分的状态(如 CBC 模式的 IV 链)。适用于大数据流或网络传输。

简单说——单包适合”一次性处理”的场景,多包适合”流式处理”的场景。

SDF_VENDOR 结构体:厂商适配的”翻译层”

SDF_VENDOR 结构体本质上是厂商差异的适配层,负责:

  • 算法 ID 映射:将标准算法标识映射到厂商自定义 ID
  • 能力位映射:将标准能力位图转译为厂商的能力定义
  • ECCCipher 编解码:处理 SM2 加密输出格式(C1C3C2 vs C1C2C3)
  • 错误码解释:将厂商错误码标准化

它和 SDF_METHOD 的关系是:METHOD 定义”能做什么”(接口契约),VENDOR 定义”这家厂商怎么做”(差异映射)。

sdf_lib.c vs sdf.c

GmSSL 中两个文件的职责不同:

  • sdf_lib.c:标准 SDF_* 的统一实现与分发层。适配 vendor,调用厂商库,是 SDF 标准的直接映射。
  • sdf.c:面向应用的易用封装层。隐藏算法 ID、结构体细节,提供默认安全参数、自动资源管理和一站式组合操作。

为什么要分层?降低使用复杂度。应用开发者不需要理解设备句柄、会话管理、算法映射等细节——sdf.c 处理这些,暴露更简洁的接口。

为什么不走 EVP/Engine/Provider?

GmSSL 的 SDF 完全独立于 EVP/provider/engine 体系,原因有三:

  1. 依赖收敛与可移植性:engine 已被 OpenSSL 弃用(3.0+),provider 接口演进成本高且 SDF 的设备/会话/索引语义与 EVP 不完全匹配。
  2. 性能与可控性:直连 SDF 性能更直接,不经过 EVP 层的额外抽象。
  3. 中国密码生态优先:国密硬件厂商更习惯于 SDF 标准接口,而非 OpenSSL 的 EVP/provider 范式。

换句话说:SDF 是业务侧协议/接口层,EVP 是算法执行层。它们不是竞争关系,而是适配关系。软实现 SDF 时调用 EVP 属于”用成熟算法引擎填充规范接口”的标准适配模式。

软件实现 SDF 的架构设计

分层思路

1
2
3
4
5
[ 应用 / 命令行工具 ]
↓ 调用
[ SDF API (对外接口:设备/会话/句柄) ]
↓ 由软实现填充
[ 密码原语提供者:EVP(软件) / 硬件驱动 ]

软件实现的目标:不用真实硬件,用 OpenSSL/Tongsuo 已有的 EVP/随机/ECC 功能,在内存中模拟设备→会话→密钥句柄→操作。

分阶段实现路线

阶段 目标 涉及函数
1. MVP 打通生命周期 OpenDevice, OpenSession, GenerateRandom, Close
2. 对称密钥 加解密 + MAC ImportKeyWithKEK, DestroyKey, Encrypt, Decrypt, CalculateMAC
3. ECC 公钥导出+签名 国密签名能力 ExportSignPublicKey, InternalSign_ECC
4. ECC 加解密 SM2 Encrypt/Decrypt InternalEncrypt_ECC, InternalDecrypt_ECC
5. 扩展 动态密钥生成 GenerateKey, GenerateKeyPair
6. 访问控制 私钥权限管理 Get/ReleasePrivateKeyAccessRight
7. 完善 错误码、线程安全、资源清理 全部接口补全

每个阶段完成后写极简自测验证,再进入下阶段。

核心数据结构

设备(Device):代表一个逻辑加密设备。软件实现中实际就一个全局实例,包含引用计数、读写锁和预置的内部 ECC 私钥数组。

会话(Session):提供临时操作上下文。引用 Device,维护自己的对称密钥列表(会话范围内的密钥句柄),有独立锁。

密钥句柄(Key Handle):会话内有效的对称密钥对象。包含算法 ID、密钥数据、是否来自 KEK 包封等状态。

为什么强烈建议用 EVP 而非手写算法?

方案 优点 风险
全部用 EVP 快速、安全、维护轻 需理解 EVP 抽象与参数映射
混合封装 80% EVP + 格式适配 需要理解底层格式
完全自写 SM2/SM3/SM4 理论上最大自主 极高的安全与时间成本、侧信道漏洞

对于 SDF 的软实现,90% 的函数内部可以直接调用 EVP:RAND_bytes(随机数)、EVP_Cipher*(对称加密)、EVP_DigestSign*(SM2 签名)、EVP_PKEY_encrypt(SM2 加密)。只有设备/句柄管理是你自己的逻辑。

如果换真实硬件,只需替换方法表中的函数指针,不再调 EVP 而调硬件驱动函数,上层代码不需要修改。

两种绑定策略:DSO vs 静态链接

DSO(运行时动态绑定)

1
2
sdf_dso = DSO_load(NULL, "sdf", NULL, 0);
sdfm.OpenDevice = DSO_bind_func(sdf_dso, "SDF_OpenDevice");
  • DSO_load ≈ dlopen / LoadLibrary:把 libsdf.so 映射进进程地址空间
  • DSO_bind_func ≈ dlsym / GetProcAddress:查找符号地址并填入函数指针表

优点:可插拔、多厂商/热替换。缺点:运行时查找有微小开销。

编译期静态链接

直接把实现编译进库,函数指针直接赋值为本地函数。

优点:性能最优(编译期类型检查、零间接调用开销)。缺点:缺少运行时灵活性。

实际工程中两条路径并存——构建脚本决定走哪条。

SoftSDF 迁移

GmSSL 的子项目 SoftSDF 提供了一套软件模拟 SDF 的实现(libsoftsdf.so)。要把它集成到 Tongsuo 体系,需要理解两个配套组件:

  • softsdfinit:初始化和密钥生成工具,用于生成 KEK、SM2 密钥对等文件——模拟硬件设备的初始化过程,只负责”造钥匙”。
  • libsoftsdf.so:实现 SDF 标准接口的动态库,负责”用钥匙办事”——读取 softsdfinit 生成的密钥文件,提供加解密、签名、密钥管理等接口。

两者配合模拟真实 SDF 设备的完整流程:先初始化设备(生成密钥文件),再加载动态库执行业务操作。

实现思路关键节点(8月18日)

到 8 月中旬,总体思路明确:

  1. 把 SDF 与 TSAPI 部分移出来,新建独立项目
  2. 用 EVP 接口在编译期静态绑定,手动实现 SDF 软实现
  3. 同时学习 SoftSDF(CMake 构建),尝试动态加载到 Tongsuo
  4. 软实现调通后,开发命令行应用
  5. 最终完成 69 个接口的全面实现

这个位置处于 Tongsuo 库的腰部:向上打通命令行应用,向下完成接口标准适配与动态库解析。


开源之夏02:SDF接口规范与软件实现设计
https://47.108.189.123/2025/08/10/开源之夏/02-SDF接口规范与软件实现设计/
Author
Dong
Posted on
August 10, 2025
Licensed under