SDK架构设计:模块划分与功能定义

做合规治理SDK,说白了就是把一堆密码算法、密钥管理、日志审计、合规策略这些东西,拧成一个好用的工具包。我刚开始接手SM4集成时,第一反应是——“直接调库不就完了?”后来才发现,如果没有清晰的模块划分,后面维护起来简直是噩梦。

这一章,我们就来聊聊怎么给这个SDK“切蛋糕”。不搞花架子,直接上架构。

整体架构分层

我个人习惯把SDK分成三层:

  • 接口层:对外暴露API,用户调用的入口。要求简单、稳定、文档清晰。
  • 核心层:真正的干活模块。SM4加密/解密、密钥派生、模式选择、填充处理。这部分代码要高度内聚,尽量不依赖外部库。
  • 治理层:合规相关逻辑。密钥生命周期管理、安全策略检查、日志审计、符合国密标准(GM/T 0002、GM/T 0003等)的校验。这部分是我踩坑最多的地方。

你想想看,如果把治理逻辑散落在核心算法里,到时候改一条合规要求,得把整个SDK翻一遍。所以分层一定要清晰。

架构原则: 接口层只做参数校验和转发,核心层只做密码运算,治理层做所有跟「合规」有关的决策。三层之间通过定义好的回调接口或上下文结构体通信。

模块划分:六个核心模块

根据我在几个项目中迭代出来的经验,SM4合规SDK至少需要以下六个模块。我用一个表格来说明每个模块的职责和关键点。

模块名称核心功能个人避坑提醒
SM4引擎实现SM4加密/解密,支持ECB、CBC、CTR、GCM等模式,以及PKCS7/PKCS5填充GCM模式下认证标签长度必须固定为16字节,否则很多合规检测会挂。我以前吃过这个亏。
密钥管理生成、导入、导出、更新、销毁密钥;支持密钥层级结构(主密钥/工作密钥)密钥销毁时一定要做内存擦除(memset_s),否则敏感数据可能残留。
模式选择器根据业务场景自动选择最安全的密码模式,并提供手动覆盖接口别让用户随便选ECB模式——ECB在合规检测里通常是高危。我建议默认强制CBC或GCM。
合规校验器校验密钥强度、算法参数、随机数质量、模式安全性是否符合国密标准随机数生成必须用硬件TRNG或国密标准的DRBG,不能用rand()。
日志审计记录加密/解密/密钥操作的时间、结果、用户ID、设备指纹,支持导出日志不能包含明文密钥!我曾经见过有同事把密钥base64后记到日志里……
适配桥接适配不同操作系统(Linux/Windows/RTOS)、不同硬件平台(x86/ARM/RISC-V)的底层密码接口桥接层要抽象出统一的底层接口,否则换一个硬件平台就要改核心代码。

模块间协作关系

光有模块还不够,得知道它们怎么配合。我画了一张结构图帮你理解数据流:

SM4合规治理SDK 模块协作图 接口层 API SM4引擎 加解密 / 模式 / 填充 密钥管理 生成/销毁/层级 模式选择器 自动选安全模式 合规校验器 参数/随机数/密钥 日志审计 操作/结果/用户 适配桥接 OS/硬件适配 协作说明 API层负责调度:接收调用 → 合规校验器检查参数 → 密钥管理获取密钥 → 模式选择器确定模式 → SM4引擎执行运算 → 日志审计记录结果
设计小技巧: 六个模块都通过「抽象接口」与API层交互。这样如果以后要换底层SM4实现(比如从软实现换成硬件加速),只需要改SM4引擎模块的内部实现,API层和其他模块完全不用动。我在实际项目中这么搞,换过一次硬件平台,两天就搞定了。

功能定义:接口粒度要细

功能定义的时候,最容易犯的错误是把接口做得过于「万能」。举个例子:

// 反面教材——一个函数做所有事
int sm4_do_everything(
    int operation,           // 0加密 1解密 2密钥生成 ...
    const uint8_t *key,
    const uint8_t *iv,
    const uint8_t *in, size_t in_len,
    uint8_t *out, size_t *out_len,
    int mode,                // 0 ECB 1 CBC ...
    int padding              // 0无 1PKCS7
);

这种接口看着好像「灵活」,但谁用谁想骂。参数太多、语义模糊、容易传错。我建议拆分成多个细粒度函数:

// 加密接口
int sm4_cbc_encrypt(const sm4_ctx_t *ctx,
                    const uint8_t *plain, size_t plain_len,
                    uint8_t *cipher, size_t *cipher_len);

// 解密接口
int sm4_cbc_decrypt(const sm4_ctx_t *ctx,
                    const uint8_t *cipher, size_t cipher_len,
                    uint8_t *plain, size_t *plain_len);

// 密钥生成
int sm4_keygen(sm4_ctx_t *ctx, const uint8_t *seed, size_t seed_len);
重要提醒: 每个函数都要做NULL指针和长度校验,别指望调用方传的都是合理参数。我曾经在一个项目里因为没检查输入指针为空,导致线上模块直接崩溃——那晚的on-call电话,你懂的。

治理层的特殊设计

治理层是SDK的「门卫」。每次密码操作之前,它都要问三个问题:

  • 密钥合规吗? 密钥长度是否128位?是否来自合法的密钥源?有没有过期?
  • 算法参数合规吗? 模式是否在白名单里?IV长度是否正确?填充方式是否符合要求?
  • 环境合规吗? 随机数质量是否达标?日志是否已开启?是否处于安全模式?

把这些检查逻辑统一放在合规校验器里,其他地方完全不用操心。这是我从一次合规审计中悟出来的——原来审计老师只看这一个模块就能判断整个系统是否合规。所以治理层的代码质量,某种意义上决定了你SDK的「命」。

治理层关键指标: 每次密码操作的合规检查耗时控制在1ms以内(纯软件环境)。如果超过这个值,说明你有不必要的全量扫描,应该考虑缓存或增量校验。

模块间的数据流

最后,我列一下典型的数据流顺序。这对你搭建实际代码架构很有帮助:

  1. 用户调用API(如 sm4_encrypt)。
  2. API层解析参数,调用合规校验器。
  3. 合规校验器检查密钥是否有效、模式是否允许、环境是否安全。
    ——如果失败,直接返回错误码,日志审计模块记录失败事件。
  4. 合规通过,API层调用密钥管理模块获取实际密钥。
  5. 密钥管理返回工作密钥(如果是层级结构,需要先解密主密钥得到工作密钥)。
  6. API层拿着密钥和参数,调用SM4引擎执行加密。
  7. SM4引擎运算完成,返回结果。
  8. API层调用日志审计模块,记录成功操作。
  9. 最终结果返回给用户。

这个流程里,每个步骤都可能被适配桥接层拦截——比如在RTOS上,很可能没有文件系统来存日志,这时候适配桥接就要把日志写入环形缓冲区或者通过串口输出。

我的习惯: 在开发初期,先把适配桥接层做成一个空函数(直接返回成功),等核心逻辑测试通过后再实现具体的平台适配。这样能先验证算法和治理逻辑的正确性,再处理平台差异。不然两个事情搅在一起,debug会非常痛苦。

好了,模块划分和功能定义就聊到这儿。记住:好的SDK架构就像一栋房子的梁柱,后面所有的工作都在这上面搭建。设计阶段多想10分钟,编码阶段就能省下10小时。