当前位置: 首页 > java >正文

API接口签名和敏感信息加密使用国密SM方案

这或许是一个讨论文,因为我也不确定哪种方案更好。

签名方式使用国密sm,用到的有sm2、sm3、sm4。因为国家对这方面的安全检查越来越严格了,有要求使用国密。我们项目最近也被密码安全部门检查过,如果不想以后要整改,建议加密相关的需求最好使用国密算法。

sm2是类似于RSA的非对称加密算法,sm3是类似于md5的摘要算法,sm4是类似AES的对称加密算法。

接口中签名使用的参数

appId:appId

nonce:随机数

timestamp:时间戳

message:报文json字符串

这些参数的含义就不在此阐述了。

国密的用法

使用sm3对请求参数进行摘要签名,防止数据篡改。

使用sm4对敏感信息字段加密,比如登录接口有username,password。就可以对password字段加密。如果没有敏感信息则无需加密。

使用sm2对密钥或者摘要数据加密。

sm2的用法我是比较纠结的一点,因为非对称加密算法很复杂,需要占用一部分性能。

sm4的密钥的使用有两种方式,1是客户端每次请求生成一个新的sm4密钥,对敏感信息字段加密后,把密钥放入待签名字符串中一起进行签名,然后用sm2公钥加密此密钥,放入请求头中传给后端。2是固定使用一个sm4密钥,前后端各自保存,不需要放入带签名字符串中和请求头中。

下面说方案。

方案1:

客户端保存appId、sm2公钥。

客户端每次请求生成sm4密钥,仅对敏感信息字段加密,然后放入待签名字符串中一起进行sm3摘要,再使用sm2公钥对sm4密钥加密放入请求头。后端先使用sm2私钥对sm4密钥解密,然后拼接参数进行sm3摘要,最后和前端传来的sm3摘要做对比。

前端代码如下,使用js模拟

/**** @param data 请求参数* @param encryptKey sm4密钥*/
export const apiSign = (data: any, encryptKey: string) => {const appId: string = "appId";const publicKey: string = "sm2公钥";const nonce: string = randomStr(32, true);const timestamp: string = new Date().getTime().toString();const message: string = JSON.stringify(data);const signStr: string = `appId=${appId}&nonce=${nonce}&timestamp=${timestamp}&message=${message}&encryptKey=${encryptKey}`;const signature = SmUtils.sm3(signStr);const encryptKeyResult = SmUtils.sm2Encrypt(encryptKey, publicKey);const header: Map<string, string> = new Map();header.set("appId", appId);header.set("nonce", nonce);header.set("timestamp", timestamp);header.set("signature", signature);header.set("encryptKey", encryptKeyResult);return header;
};

响应的时候后端使用前端传来的sm4密钥加密敏感信息字段,然后签名方式同请求时一致,只是不需要再传回来sm4密文了,前端还是用当前请求生成的sm4密钥解密敏感信息字段。

方案2:

客户端保存appId、sm2公钥。

客户端每次请求生成sm4密钥,仅对敏感信息字段加密,然后放入待签名字符串中一起进行sm3摘要,再使用sm2公钥对摘要进行加密,再使用sm2公钥对sm4密钥加密放入请求头。后端先使用sm2私钥对sm4密钥解密,再使用sm2私钥对签名字段解密得出摘要,然后拼接参数进行sm3摘要,最后和前端传来的sm3摘要做对比。

此方案对比方案1多进行了一次sm2,势必会占用一些性能资源。

/**** @param data 请求参数* @param encryptKey sm4密钥*/
export const apiSign = (data: any, encryptKey: string) => {const appId: string = "appId";const publicKey: string = "sm2公钥";const nonce: string = randomStr(32, true);const timestamp: string = new Date().getTime().toString();const message: string = JSON.stringify(data);const signStr: string = `appId=${appId}&nonce=${nonce}&timestamp=${timestamp}&message=${message}&encryptKey=${encryptKey}`;const sm3Str = SmUtils.sm3(signStr);const signature = SmUtils.sm2Encrypt(sm3Str, publicKey);const encryptKeyResult = SmUtils.sm2Encrypt(encryptKey, publicKey);const header: Map<string, string> = new Map();header.set("appId", appId);header.set("nonce", nonce);header.set("timestamp", timestamp);header.set("signature", signature);header.set("encryptKey", encryptKeyResult);return header;
};

响应的时候后端使用前端传来的sm4密钥加密敏感信息字段,然后签名方式同请求时一致,只是不需要再传回来sm4密文了,前端还是用当前请求生成的sm4密钥解密敏感信息字段。

方案3:

客户端保存appId、sm2公钥 、sm4密钥。

使用固定sm4密钥,仅对敏感信息字段加密,前后端各自保存,不需要一起进行sm3摘要,也不需要放入请求头。拼接带签名字符串进行sm3摘要,再使用sm2公钥对摘要进行加密。后端使用sm2私钥对签名字段解密得出摘要,拼接参数进行sm3摘要,最后和前端传来的sm3摘要做对比。

此方案的缺点是使用固定sm4密钥,安全性不如每次生成。

/**** @param data 请求参数*/
export const apiSign = (data: any) => {const appId: string = "appId";const publicKey: string = "sm2公钥";const nonce: string = randomStr(32, true);const timestamp: string = new Date().getTime().toString();const message: string = JSON.stringify(data);const signStr: string = `appId=${appId}&nonce=${nonce}&timestamp=${timestamp}&message=${message}`;const sm3Str = SmUtils.sm3(signStr);const signature = SmUtils.sm2Encrypt(sm3Str, publicKey);const header: Map<string, string> = new Map();header.set("appId", appId);header.set("nonce", nonce);header.set("timestamp", timestamp);header.set("signature", signature);return header;
};

响应的时候后端使用固定sm4密钥加密敏感信息字段,然后签名方式同请求时一致。

这三种方案我倾向于方案2,但方案2使用了2次sm2,对性能有一定的影响。也希望和各位大佬讨论一下,是否有更合适的方案?

http://www.xdnf.cn/news/15862.html

相关文章:

  • 数据结构——时间复杂度
  • Python知识点4-嵌套循环break和continue使用死循环
  • 论文分享(一)
  • Spring MVC上下文容器在Web容器中是如何启动的(源码深入剖析)?
  • Self-Consistency:跨学科一致性的理论与AI推理的可靠性基石
  • Linux操作系统从入门到实战(十一)回车换行问题与用户缓冲区问题
  • 通俗易懂神经网络:从基础到实现
  • 学习日志15 python
  • 零基础入门 AI 运维:Linux 部署全栈项目实战(MySQL+Nginx + 私有化大模型)
  • 【1】计算机视觉方法(更新)
  • selenium4 web自动化测试
  • 面向对象基础笔记
  • QFutureInterface和QFuture间联系与区别
  • 《计算机网络》实验报告五 DNS协议分析与测量
  • 两个数据表的故事第 2 部分:理解“设计”Dk
  • ThinkPHP8极简上手指南:开启高效开发之旅
  • 项目案例:苏宁易购评论获取
  • 民法学学习笔记(个人向) Part.1
  • 【智能协同云图库】第一期:用户管理接口设计与功能实现
  • 【Java学习|黑马笔记|Day18】Stream流|获取、中间方法、终结方法、收集方法及其练习
  • 超大整数任意进制之间在线转换工具
  • 剑指offer67_构建乘积数组
  • 周志华《机器学习导论》第11章 特征选择与稀疏学习
  • PyTorch里的张量及张量的操作
  • [前端技术基础]CSS选择器冲突解决方法-由DeepSeek产生
  • 前端的测试
  • 如何实战优化SEO关键词提升百度排名?
  • 深度学习图像分类数据集—百种病虫害分类
  • 【KDD2025】时间序列|Merlin:不固定缺失率下时间序列预测新SOTA!
  • C++ - 仿 RabbitMQ 实现消息队列--服务端核心模块实现(一)