记一次国密落地的经历
前言
在一般意义上的后台服务中,身份认证可以保证数据源没有问题,完整性校验可以保证数据没有经过窃听者的篡改,但我们还要防止窃听者知道数据的内容,这就还需要加解密来帮助我们守住最后一道围墙
而在私有云交付的环境中,我们无法用现有的公司平台加解密服务,并且按照国家、金融行业等要求,需要用国密算法实现的加解密方案
国密存在哪些问题
使用不便
最大问题是使用不便。这是由于国密不在IETF国际标准中,不同于ECDSA、ECDH、RSA等国际算法,系统中往往包含相关标准加解密方式,业务数据包通过HTTPS传输时完全不用考虑如何交换公钥,如何加解密数据。
因此现阶段使用国密必须在业务层手动进行数据加解密,相当于对数据进行一步额外的操作。
无最佳实践
确定业务层进行加解密后,应该使用哪一种国密实现、该如何进行加解密是另一个难点,且暂不存在一个最优解。国密的实现方案很多,包括TencentSM、GmSSL和KMS服务器,这也需要进一步的调研和测试来决定最终方案。
国密落地过程
国密落地分为调研、制定、实现与测试四个阶段。
调研阶段主要目的有两个:
- 找到性能高效、使用便捷并且有足够保障(维护活跃度、大厂背书、有变现渠道保证不会暴死等)的国密算法实现
- 调研公司内国密改造案例
国密算法选择
我们通过benchmarks测试评估多种国密库算法性能,最终结果如下:
可以看到TencentSM的benchmarks测试结果较为突出,也是作为我们的最终选择
国密实现方案选择
对于国密的实现,我们收集到的可行方案如下:
- XX实验室建议方案:引入额外KMS系统,独立部署管理证书、私钥,分发公钥。
- 优点:保密性好,完整的加解密方案(包括证书、信任链等待)
- 缺点:需要在业务侧缓存大量SM4密钥,对性能有影响
- XXX网关团队数据加密方案:一次链接一个SM4密钥,发送SM4加密的数据同时发送SM2公钥加密后的SM4密钥。
- 优点:过程简单
- 缺点:服务端每次数据处理都需要双解密(解密密钥后解密数据),对性能有影响
- 无侵入数据加密方案:使用双SSL证书(RSA/SM2)的HTTPS前置机作为网络入口,完全无侵入性地替换。
- 优点:完全无缝地替换国际加密协议,业务侧无感知
- 缺点:SDK侧同样需要双证书支持,但目前普遍缺乏实现;需要添加额外组件HTTPS前置机
在制定方案之前,我们总结了对解密方案的需求:
- 简单无依赖
- 性能好
- 成熟
- 支持服务器热更新SM2公钥
- 支持上报失败后数据加密落地重传
可以接受的缺点:
- 对业务侵入
于是在制定方案时我们充分考量了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钥匙对
值得注意的是:
- 为了确保解密后数据无误,同时上报原始数据MD5用以比对
- 为了确保服务器更新SM2公钥后上报仍然可以进行,我们设计了主从密钥方式,被换下的密钥并不删除,而是作为从钥继续用以解密,并且通知终端更新公钥
- 为了确保上报失败后可以重试,我们将加密数据以及meta信息落盘
该方案经过云鼎实验室同事确认可靠性,我们最终采取了该方案
重难点解决
高并发下的解密实现
由于解密过程需要用到线程相关的变量,若每次解密都去生成对应的上下文将非常耗时。同时由于在QAPM的国密方案下公钥是定期更新的,所以这里为了保证解密流程的性能,在初始化时使用SM2InitCtxWithPubKey
为每一个Worker创建了一个上下文。
1 | /** |
再通过自行实现的协程池管理并发解密任务,兼顾解密服务的稳定性和吞吐量。
1 | type Worker struct { |
避免国密接口对原有架构的侵入性
由于实现的国密方案是在项目原有的接入层微服务代码中拓展实现,为保证原有架构的完整性,避免国密接口侵入导致的额外开发量以及额外的维护成本,我们对接入层的架构进行了微调,最终通过重写fasthttp的部分方法(如BodyGunzip, MultipartFormBoundary, MultipartForm, Body等),细化对request的处理步骤,完全解耦了解密和接口具体逻辑
耗时监控
最终上线符合国密标准的接入层系统后,构建耗时监控如下:
可以看到耗时在可控范围内,且除文件上报外耗时无明显变化
总结
安全无小事,这是我第一次参与大型的加解密服务的设计与实现工作中,希望后续还会有机会丰富我浅薄的网络安全知识🤔🤔🤔