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

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.lryc.cn/news/593952.html

相关文章:

  • 上电复位断言的自动化
  • go-redis Pipeline 与事务
  • 《计算机网络》实验报告五 DNS协议分析与测量
  • Dockerfile配置基于 Python 的 Web 应用镜像
  • 随着GPT-5测试中泄露OpenAI 预计将很快发布 揭秘GPT-5冲击波:OpenAI如何颠覆AI战场,碾压谷歌和Claude?
  • 单片机启动流程和启动文件详解
  • 数组算法之【合并两个有序数组】
  • 嵌入式硬件篇---舵机(示波器)
  • 设备健康管理实施案例:从技术架构到落地效果的全栈解析
  • 嵌入式硬件篇---机械臂运动学解算(3自由度)
  • 【MySQL】索引中的页以及索引的分类
  • 全面解析MySQL(2)——CRUD基础
  • RabbitMQ面试精讲 Day 4:Queue属性与消息特性
  • UDP中的单播,多播,广播
  • RabbitMQ核心组件浅析:从Producer到Consumer
  • 30个常用的Linux命令汇总和实战场景示例
  • 使用 Pyecharts 绘制精美饼状图:从基础到高级技巧
  • nginx定期清理日志
  • Node.js:函数、路由、全局对象
  • 数据并表技术全面指南:从基础JOIN到分布式数据融合
  • 分布式文件系统04-DataNode海量数据分布式高可靠存储
  • ZooKeeper学习专栏(一):分布式协调的核心基石
  • 【橘子分布式】gRPC(编程篇-下)
  • C++STL系列之list
  • ABP VNext + Grafana Loki:集中式日志聚合
  • 【Django】DRF API版本和解析器
  • Kubernetes (K8S)知识详解
  • 基于bert-lstm对微博评论的情感分析系统设计与实现
  • JVM-Java
  • Web服务压力测试工具hey学习一:使用方法