在一般意义上的后台服务中,身份认证可以保证数据源没有问题,完整性校验可以保证数据没有经过窃听者的篡改,但我们还要防止窃听者知道数据的内容,这就还需要加解密来帮助我们守住最后一道围墙
而在私有云交付的环境中,我们无法用现有的公司平台加解密服务,并且按照国家、金融行业等要求,需要用国密算法实现的加解密方案
使用不便
最大问题是使用不便。这是由于国密不在IETF国际标准中,不同于ECDSA、ECDH、RSA等国际算法,系统中往往包含相关标准加解密方式,业务数据包通过HTTPS传输时完全不用考虑如何交换公钥,如何加解密数据。
因此现阶段使用国密必须在业务层手动进行数据加解密,相当于对数据进行一步额外的操作。
无最佳实践
确定业务层进行加解密后,应该使用哪一种国密实现、该如何进行加解密是另一个难点,且暂不存在一个最优解。国密的实现方案很多,包括TencentSM、GmSSL和KMS服务器,这也需要进一步的调研和测试来决定最终方案。
国密落地分为调研、制定、实现与测试四个阶段。
调研阶段主要目的有两个:
我们通过benchmarks测试评估多种国密库算法性能,最终结果如下:
可以看到TencentSM的benchmarks测试结果较为突出,也是作为我们的最终选择
对于国密的实现,我们收集到的可行方案如下:
在制定方案之前,我们总结了对解密方案的需求:
可以接受的缺点:
于是在制定方案时我们充分考量了HTTPS加解密方式,设计了类似的加密上报方式
基于对称密钥加密公钥的非对称加密方案,时序图如下:
sequenceDiagram
participant S as SDK
participant E as Entrance
participant A as AppConfig
Note over E: 获取或生成SM2钥匙对
E->>+E: SM2钥匙对
S-->>E: requestForPubKey()
E->>S: 返回SM2公钥
Note over S: 若错误,停止后续
S-->>E: 请求token
E->>S: token
S-->>A: requestForConfig()
A->>S: 返回配置
Note over S: 若错误,停止后续
loop 上报数据
S->>+S: 生成临时SM4钥匙
S->>E: uploadEncryptedJson()/uploadEncryptedFile()
alt 上报成功
E->>S: 成功
Note over S: 啥也不干
else 上报成功但公钥需更新(1507)
E->>S: 返回最新SM2公钥
Note over S: 更新本地公钥
else 解码失败(1508)
E->>S: 返回最新SM2公钥
Note over S: 抛弃所有数据,更新公钥
else Check-Code校验失败(1509)
E->>S: 无
Note over S: 不应该在正式SDK发生
else 上报失败
E->>S: 失败
Note over S: 缓存数据、落盘(md5、加密后钥匙(16进制字符串)、iv、加密后数据)
end
S->>-S: 销毁临时SM4钥匙
end
E->>-E: SM2钥匙对
值得注意的是:
该方案经过云鼎实验室同事确认可靠性,我们最终采取了该方案
由于解密过程需要用到线程相关的变量,若每次解密都去生成对应的上下文将非常耗时。同时由于在QAPM的国密方案下公钥是定期更新的,所以这里为了保证解密流程的性能,在初始化时使用SM2InitCtxWithPubKey
为每一个Worker创建了一个上下文。
/**
* @brief 使用SM2获取公私钥或加解密之前,必须调用SM2InitCtx或者SM2InitCtxWithPubKey函数.如果使用固定公钥加密,可调用SM2InitCtxWithPubKey,将获得较大性能提升
* @param ctx 函数出参 - 上下文
* @param pubkey 函数入参 - 公钥
*/
func SM2InitCtxWithPubKey(ctx *SM2_ctx_t, pubkey []byte) {
if ctx == nil || pubkey == nil {
panic("invalid parameter")
}
if len(pubkey) < 130 {
panic("memory len is too small")
}
C.SM2InitCtxWithPubKey(&ctx.Context, (*C.char)(unsafe.Pointer(&pubkey[0])))
}
再通过自行实现的协程池管理并发解密任务,兼顾解密服务的稳定性和吞吐量。
type Worker struct {
name string
ctx *sm.SM2_ctx_t // ThreadLocal ctx
handler WorkerHandler
}
由于实现的国密方案是在项目原有的接入层微服务代码中拓展实现,为保证原有架构的完整性,避免国密接口侵入导致的额外开发量以及额外的维护成本,我们对接入层的架构进行了微调,最终通过重写fasthttp的部分方法(如BodyGunzip, MultipartFormBoundary, MultipartForm, Body等),细化对request的处理步骤,完全解耦了解密和接口具体逻辑
最终上线符合国密标准的接入层系统后,构建耗时监控如下:
可以看到耗时在可控范围内,且除文件上报外耗时无明显变化
安全无小事,这是我第一次参与大型的加解密服务的设计与实现工作中,希望后续还会有机会丰富我浅薄的网络安全知识🤔🤔🤔