3. 加密SDK架构设计:模块划分、核心组件与API设计原则

这一章,我们来聊聊加密SDK的骨架。

前面几章我们知道了为什么要做字段级加密,也了解了整体升级流程。但说到底,SDK内部到底长什么样?我刚开始接触这类SDK时,也踩过不少坑。最典型的一个:把所有加密逻辑都塞在一个类里,结果上线一周就发现,想换加密算法都得改核心代码,测试量巨大。

后来我才明白,一个好的加密SDK,架构一定要清晰。说白了,就是要把「变」的部分和「不变」的部分拆开。

3.1 整体架构:三层两通道模型

我自己习惯把一个字段级加密SDK分为三层。你想想看,上层是业务方调用的API,中间是核心引擎处理加解密逻辑,底层是密钥管理和数据持久化。这三层之间通过两个通道交互:一个负责数据,一个负责元数据。

嗯,还得考虑网络不通怎么办。所以本地缓存是必须的。

核心原则:上层不关心底层实现,底层不感知上层业务。

你任何一个模块的修改,都不应该导致其他模块跟着改动。这听起来简单,但能做到的SDK其实不多。

我的建议: 在设计初期,先画出模块依赖图。哪个模块依赖哪个,箭头画清楚了,后面编码会顺畅很多。

下面这张图,就是我逐渐打磨出来的一套架构。它在我参与过的几个项目里都跑得很稳,你可以做个参考。

字段级加密SDK三层架构图 字段级加密SDK三层架构图 API 接入层 Java SDK 客户端 HTTP/GRPC 网关 Spring Boot Starter 核心引擎层 加解密执行器 算法路由分发器 元数据管理器 字段级加密/解密器 版本兼容处理器 异常与降级策略 存储与密钥层 密钥管理模块 数据持久化层 本地缓存与同步 核心思想:上层调用无感知,底层升级不断流

3.2 三层架构详解

3.2.1 API接入层

这一层是SDK的门面。我做过的项目里,调用方式五花八门:有Java直调、有走HTTP网关的、有通过Spring Boot Starter自动装配的。如果每一套都写一套独立逻辑,维护成本会直接爆炸。

解决办法是什么?统一抽象一个核心接口。这一层只做一件事:接收外部请求,校验参数格式,然后转成内部的数据结构,交给下一层处理。

个人经验: 接口设计一定要考虑「懒初始化」。你想想看,有些调用方可能只注册了SDK,但很久才触发一次加密。如果启动时就加载所有资源,浪费很大。我习惯用懒加载 + 连接池缓存的方式,只在第一次加密请求时才初始化核心组件。

3.2.2 核心引擎层

这一层是真正的「大脑」。它不关心你从哪来,也不关心你的密钥怎么存。它只用来执行加密、解密、重加密这些操作。

里面有几个关键模块,我一个个说。

  • 算法路由分发器: 根据元数据决定用AES还是SM4,用哪种填充模式。说白了,它就是根据「你是谁」来选择「怎么办」。
  • 版本兼容处理器: 这个我单独拎出来说。升级SDK后,旧版本加密的数据怎么处理?我遇到过一个大坑:客户端升级后,发现读不到旧密文了。后来我才加上版本协商逻辑,当检测到旧版本密文时,走一回兼容解密链路。
  • 异常与降级策略: 加密服务挂了怎么办?我们团队的规定是:加密失败必须抛异常,不能静默返回明文;但解密失败可以降级(配置降级策略),保证业务连续性。
曾经踩过的坑: 有一版设计里,我们把算法路由的逻辑和字段处理逻辑写在了同一个类里。结果导致一个需求改动,要同时改路由规则和字段逻辑,测试非常艰难。后来我强制要求两个模块通过事件通信,才把耦合降下来。

3.2.3 存储与密钥层

这一层是基石。底层做不好,上层再漂亮也是白搭。

密钥管理模块是最敏感的。我建议密钥从不落盘——除非你用的是HSM(硬件安全模块)做本地加密。像我们团队,密钥全都通过远程密钥服务获取,且每次获取都要校验签名。本地只缓存加密后的密钥片段,不缓存明文密钥。

总结下来, 数据持久化层的核心职责包括:

  • 存储加密后的字段密文(通常是VARCHAR(MAX)类型)
  • 存储元数据版本号字段(比如一个表字段带一个版本标签)
  • 存储密钥的唯一标识(主密钥ID)

3.3 API设计原则:六字真言

接口设计这块,我有一套很简单的原则,就六个字:「对称、幂等、降级」。

原则说明我的实践
对称加密和解密的接口参数结构必须一致encrypt(data) → decrypt(data) 不用调整参数顺序或类型
幂等同样的入参,重复调用结果不变特别是重加密接口,必须保证多次执行后密文一致
降级异常时有明确的回退策略我习惯在接口文档里写明:哪些异常抛给调用方,哪些内部吞掉走降级
一个小技巧: 所有对外的API,返回值里加一个"status"字段,而不是只返回密文字段。这样调用方可以通过status判断是正常结果还是降级结果,减少误判。

3.4 元数据与版本管理机制

这块是架构里容易被忽视的。其实元数据管理好了,版本升级才能平滑。

元数据里存什么?最少要包含:字段标识、加密算法类型、密钥ID、版本号、是否启用。每次SDK升级,版本号跟着变。解密时,根据元数据里的版本号,自动选择对应的解密逻辑。什么叫自动化升级?这就是关键。

核心观点: 字段级加密SDK的架构设计,本质上是在做「依赖倒置」。上层依赖的是抽象层(核心引擎的接口),不是具体实现。只要你遵循这个原则,版本升级就是替换一个模块的事情,不需要改调用方代码。

我个人习惯在第一版设计中就把元数据管理做成可插拔的。后续如果你想换密钥存储方式、或者换一种元数据读取来源(比如从数据库切到Redis),只需要替换一个模块的注册实现,其他地方完全不需要动。

嗯,怎么样?这一套架构如果你看懂了,后面讲具体升级回滚流程时,你就能明白为什么有些步骤必须做,为什么有些步骤可以跳过。