第4章 加密SDK整体架构设计:分层、模块与接口

说到加密SDK的整体架构,我个人习惯先画一张"地图"。没有这张图,后面写代码就容易跑偏。我之前接手过一个项目,团队上来就写算法封装,结果到了对接阶段才发现——iOS和Android的调用方式完全不一样,密钥存储的逻辑也散落在各个角落。嗯,后来花了整整两周重构。

所以这一章,我想跟你聊聊我怎么看待加密SDK的分层设计、模块划分,以及接口抽象这件事。

4.1 分层架构:把复杂度关进笼子里

加密SDK的分层,说白了就是把不同职责的代码放在不同的楼层。每一层只关心自己的事,不越界。我通常把架构分成四层:

层级 名称 核心职责
L1 接口暴露层 对外提供统一API,处理参数校验与错误码映射
L2 加密服务层 封装业务逻辑,比如签名流程、密钥协商、会话管理
L3 算法插件层 管理具体算法实现,支持SM2/SM4/AES/RSA等切换
L4 密钥存储层 处理密钥生命周期、安全存储、硬件隔离

你想想看,如果我把密钥存储的逻辑直接写在接口层里,一旦要换存储方式(比如从Keychain换成TEE),就得改所有接口。分层之后,只需要动L4。

这就是分层的价值——控制变更的波及范围。

4.2 模块划分:每个模块各司其职

分层定好之后,接下来就是划模块。我见过太多项目把"加密工具类"写得又臭又长,一个类里既有密钥生成又有签名校验还有随机数生成。这不是架构,这是大杂烩。

我建议按功能领域来切:

  • 核心模块:对称加密、非对称加密、哈希、消息认证码。这些是地基,不依赖任何业务逻辑。
  • 协议模块:比如TLS握手、国密SSL、自定义签名协议。这层负责把核心算法串成流程。
  • 密钥管理模块:密钥生成、导入、导出、轮换、销毁。注意,这里不关心算法具体怎么算,只关心密钥怎么活。
  • 安全存储模块:对接平台安全能力,比如iOS的Keychain、Android的Keystore、Windows的DPAPI。
  • 性能与调试模块:性能打点、日志埋点、错误码映射。线上排查问题全靠它。

模块划分的一个原则:每个模块最多只能依赖它正下方的模块,不能跨层依赖。比如密钥管理模块不能直接调用安全存储模块底层的硬件接口,必须通过密钥存储层暴露的抽象接口。

4.3 接口抽象:让上层代码不再感知下层变化

接口抽象这一块,我踩过不少坑。我曾经在一个项目中,直接把OpenSSL的EVP接口暴露给上层调用方,结果后来要切换到BoringSSL,所有调用方都得改代码。这教训太深刻了。

所以我现在这么干:

  1. 定义抽象工厂接口:ICryptoFactory,负责创建算法实例。
  2. 定义算法能力接口:IBlockCipher、IAsymmetricCipher、IHash。
  3. 定义密钥容器接口:ISecureKey,不暴露密钥明文,只暴露操作句柄。

举个例子,这是我最常用的一套抽象:

// 对称加密接口
public interface IBlockCipher {
    byte[] encrypt(byte[] plaintext, byte[] iv) throws CryptoException;
    byte[] decrypt(byte[] ciphertext, byte[] iv) throws CryptoException;
    String getAlgorithm(); // 返回 "SM4-CBC" 或 "AES-GCM"
}

// 工厂接口
public interface ICryptoFactory {
    IBlockCipher createBlockCipher(String algorithm, ISecureKey key);
    IAsymmetricCipher createAsymmetricCipher(String algorithm, ISecureKey key);
    IHash createHash(String algorithm);
}

你想想看,上层业务代码只需要面向这些接口编程。底层换算法库?换平台?只需要重新实现一套工厂和接口,上层一行代码都不用动。

4.4 结构总览:一张图说清楚

下面这张图把整个架构串起来了。我建议你先看整体,再回来看细节。

加密SDK分层架构与模块关系图 加密SDK分层架构总览 接口暴露层 统一API入口 · 参数校验 · 错误码映射 · 多语言绑定 加密服务层 签名/验签流程 · 密钥协商 · 会话管理 · 文件加解密流程 算法插件层 SM2/SM4 · AES/RSA · SHA系列 · HMAC · 国密与国际化算法库 密钥存储层 密钥封装接口 · Keychain/Keystore/Hardware隔离 · 密钥生命周期管理 安全边界 关键设计思路 每层只依赖正下层,不跨层调用 接口抽象隔离平台差异与算法库差异 典型应用场景 金融支付 · 即时通讯 · 数据合规存储 跨平台加密SDK · 物联网安全组件

这张图里,从左边的"安全边界"你能看到:每一层之间的交互是严格向下的。上层需要什么,通过接口向下请求;下层给什么,上层不关心具体是怎么实现的。

4.5 实践建议:从接口到实现,步步为营

我记得有一次做跨平台加密SDK,团队在接口抽象上发生了分歧。有人觉得接口越多越灵活,有人觉得接口越少越简单。后来我拿出了一个折中方案:

  • 面向场景暴露接口:比如sign(data, keyId)、encryptFile(filePath, keyId),而不是encrypt(data, algorithm, mode, padding, key)。
  • 细节参数由配置对象承载:比如CryptoConfig里包含算法、模式、填充方式等,上层不需要逐个传参。
  • 错误码统一收敛:不管底层抛什么异常,都映射到SDK自己的错误码体系里。用户不用关心OpenSSL的错误码还是Keychain的错误码。

一个小技巧:在接口层加一层"样板代码",比如参数非空校验、长度校验、日志打印。这样下层实现就可以专注做加密逻辑,不用重复写校验代码。

4.6 我曾经踩过的坑

我曾经在做一个Android端的加密SDK时,直接用了Android原生的KeyStore接口,结果发现不同厂商的KeyStore实现细节不一致,有的不支持SM2,有的签名返回结果格式不同。唉,那段时间天天跟兼容性问题作斗争。

后来我学乖了:凡是涉及平台特性的部分,一定在抽象接口后面加一个适配层。比如密钥存储,先定义ISecureStorage接口,再分别实现AndroidKeyStoreStorage、iOSKeychainStorage、SoftwareFallbackStorage。这样即使某个平台的实现有问题,也只需要改一个文件。

警惕"过度抽象":不要一上来就想着抽象成万能接口。先针对当前业务写清楚,之后再提取共性。我见过有人提前做了6层抽象,结果业务变了,每一层都得改,还不如不抽象。

好了,这一章我们聊了分层、模块和接口抽象。说白了,就是把加密SDK想象成一栋楼:地基(存储层)要稳,承重墙(接口层)要直,房间(模块)要划分清楚。这样做出来的SDK,才能在多个语言、多个平台上长期稳定运行。