Web应用加密数据传输方案

目录

概述

最初的方案

改进后的方案

秘钥的过期时间


概述

     介于公司最近发布了一个面向C端用户的Web系统,为防止前端调用后端的API接口时,数据传输的内容轻易的被黑客获取,而设计的一个前后端数据加密传输方案

最初的方案

     在最开始,我们采用了最传统的AES对称加密的方式来加密API数据,前后端共用一个秘钥。具体操作流程为前端用秘钥将请求报文加密后发送到后端,后端在过滤器中同一个秘钥来完成解密请求报文;后端在响应阶段采用同一个秘钥对想用报文加密后发送到前端,前端收到响应报文后完成响应报文的解密。但是这样做有一个非常明显的弊端,就是前端需要将这个秘钥在前端的代码中写死,一旦黑客在浏览器中打开F12搜索关键词 AES,很容易找我们设置的秘钥,从而导致整个加解密全部暴露。

改进后的方案

      为解决秘钥容易在前端暴露的问题,我们重新设计了整个加解密的流程,整个过程使用了两对RAS非对称加密的秘钥。一对给前端加密、后端解密用(后端秘钥),另一对给后端加密,前端解密用(前端秘钥)。具体为:

1. 前端在加载主页时,以伪装成加载图片内容(base64字符串)的形式先向后端发送两个请求,分别用于获取前段解密用的私钥、加密用的公钥以及秘钥对应的摘要。注意,这里分别获取的公钥、私钥并不是同一对,而是两对秘钥中分别取一个。返回的秘钥伪装成一个base64格式的图片流,并且使用了一定的扰动处理。

@Operation(summary = "获取公钥")@GetMapping("/welcome")public RespEntity<String> welcome() throws Exception {String s1 = GlobalStatus.getRequest().getHeader("s1");String publicKey = "";if(StringUtils.isNotBlank(s1)){publicKey = rsaService.getPublicKey(CodecProperties.SERVER_RSA_KEY_PRE,s1);if(StringUtils.isBlank(publicKey)){return RespEntity.ok();}}else{// 生成一对公私钥Map<String, String> keyMap = rsaService.getRsa(CodecProperties.SERVER_RSA_KEY_PRE,s1);publicKey = keyMap.get(RsaUtils.PUBLIC_KEY);s1 =  Sha1Utils.getSHA1Digest(publicKey);}// 将秘钥进行混淆后拼上摘要后返回前端String firstStr = publicKey.substring(0,3);String secondStr = publicKey.substring(3,publicKey.length()-3);String thirdStr = publicKey.substring(publicKey.length()-3);String simulatePublicKey = StringUtils.getRandomLetterString(3) +  thirdStr + secondStr + firstStr + "_" + s1 + StringUtils.getRandomLetterString(4);byte [] simulatePublicKeyArray = simulatePublicKey.getBytes();String keyBase64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(simulatePublicKeyArray);return RespEntity.ok(keyBase64);}@Operation(summary = "获取私钥")@PostMapping("/getUsefulImg")public RespEntity<String> getUsefulImg() throws Exception {String s2 = GlobalStatus.getRequest().getHeader("s2");String privateKey = "";if(StringUtils.isNotBlank(s2)){privateKey = rsaService.getPrivateKey(CodecProperties.CLIENT_RSA_KEY_PRE,s2);if(StringUtils.isBlank(privateKey)){return RespEntity.ok();}}else{// 生成一对公私钥Map<String, String> keyMap = rsaService.getRsa(CodecProperties.CLIENT_RSA_KEY_PRE,s2);privateKey = keyMap.get(RsaUtils.PRIVATE_KEY);String publicKey = keyMap.get(RsaUtils.PUBLIC_KEY);s2 =  Sha1Utils.getSHA1Digest(publicKey);}// 将秘钥进行混淆后拼上摘要后返回前端String firstStr = privateKey.substring(0,privateKey.indexOf("/"));String secondStr = privateKey.substring(privateKey.indexOf("/"),privateKey.lastIndexOf("/")+1);String thirdStr = privateKey.substring(privateKey.lastIndexOf("/")+1);String simulatePrivateKey = StringUtils.getRandomLetterString(3) +  thirdStr + secondStr + firstStr + "_" + s2 + StringUtils.getRandomLetterString(4);byte [] simulatePrivateKeyArray = simulatePrivateKey.getBytes();String keyBase64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(simulatePrivateKeyArray);return RespEntity.ok(keyBase64);}

2. 考虑到RSA非对称加密的性能较差,因此对报文内容的加密我们仍然使用AES对称加密,前端每次在加密请求报文时,会生成一对新的AES秘钥,对请求报文加密,然后使用获取到的RAS非对称加密的私钥对这个AES的key做加密;那么请求报文中,将会有加密后的data、私钥的摘要、公钥的摘要、加密的AES的key发送给后端。

3. 后端在收到请求报文后,根据私钥的摘要找到对应私钥的公钥,然后以公钥对加密的AES key进行解密,然后再以AES的key对请求报文进行解密。

4. 后端在加密响应报文时,同样的会生成一对新的AES的key,以这个key对响应报文加密,然后以后端的私钥对AES key  加密;因此,后端的响应报文也会有 加密后的data、私钥的摘要、加密的AES的key三个字段。

5. 前端在收到响应报文后,先根据摘要校验后端加密的公钥与前端解密的私钥是否是一对,如果不是,则要重新获取新的私钥。

package com.vteam.uap.core.filter;import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.vteam.uap.cache2.strategy.HashMapContainer;
import com.vteam.uap.cache2.strategy.StringContainer;
import com.vteam.uap.common.util.*;
import com.vteam.uap.core.service.RsaService;
import com.vteam.uap.jwt.exception.ParamCheckException;
import com.vteam.uap.security.codec.CodecProperties;
import com.vteam.uap.security.global.SecurityGlobalStatus;
import com.vteam.uap.security.httpWrapper.RequestWrapper;
import com.vteam.uap.security.httpWrapper.ResponseWrapper;
import com.vteam.uap.util.SpringContextUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.Filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerExceptionResolver;import java.io.IOException;
import java.util.Map;
import java.util.Objects;/*** @author Miller.Lai* @description:  加解码过滤器* @date 2023-12-15 16:46:03*/
@Slf4j
public class CodecFilter implements Filter {@Resourceprotected CodecProperties codecProperties;@Resourceprivate StringContainer stringContainer;@Resourceprivate HashMapContainer hashMapContainer;@Resourceprivate RsaService rsaService;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 请求解密if (codecProperties.isApiEnable() && !isIgnore()  &&(( "POST".equalsIgnoreCase(((HttpServletRequest)request).getMethod().trim()) && "application/json".equals(request.getContentType().trim().toLowerCase()))|| !"POST".equalsIgnoreCase(((HttpServletRequest)request).getMethod().trim())) ){// 包装响应对象ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);String url = ((HttpServletRequest) request).getRequestURI();String userId = SecurityGlobalStatus.getUserid();HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver) SpringContextUtils.getBean("handlerExceptionResolver");// 如果是post请求,则请求体解密,get请求不做处理if("POST".equals(((HttpServletRequest)request).getMethod().trim().toUpperCase())){RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);String bodyStr =  requestWrapper.getRequestBody();if(StringUtils.isNotBlank(bodyStr)){JSONObject bodyJson = JSONObject.parseObject(bodyStr);Object data = bodyJson.get("data");// s1是服务端秘钥的摘要String s1 = ((HttpServletRequest) request).getHeader("s1");if(StringUtils.isEmpty(s1)){log.error("服务端秘钥摘要缺失,需要添加请求头:s1");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}// 从请求参数 uuid 中拿到 AES解密的 key 和 ivString cryptUuid = (String)bodyJson.get("u");if( StringUtils.isBlank(cryptUuid) ){log.error("加密的k、v缺失,body中必须要有u字段");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}String privateKey = rsaService.getPrivateKey(CodecProperties.SERVER_RSA_KEY_PRE,s1);if(StringUtils.isBlank(privateKey)){log.error("未找到s1对应的秘钥信息");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}String k = null;String v = null;try{String uuid = RsaUtils.privateDecrypt(cryptUuid, privateKey);String[] uuidArray = uuid.split("_");k = uuidArray[0];v = uuidArray[1];}catch (Exception e){log.error("AES的k、v解密失败");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}if(data instanceof String && !((String) data).trim().isEmpty()){if (log.isDebugEnabled()) {log.debug("解密 API 请求消息:type={}, requestBody={}",requestWrapper.getMethod(), bodyJson.toJSONString());}String plainText = AesUtils.decrypt((String) data, AesUtils.Mode.API,k,v,codecProperties.isDbEnable());Object dataObj = "";try{dataObj = JSON.parseObject(plainText);Object timestampObj = ((JSONObject)dataObj).get("timestamp");if(timestampObj == null ){log.error("请求时间戳缺失");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}long timestamp = (long)timestampObj;long currentTimeMillis = System.currentTimeMillis();long currentTimeMicros = currentTimeMillis * 1000;// 请求时间戳是否过期,超过1分钟过期if((currentTimeMicros -timestamp)/(1000 * 1000)>60){log.error("请求时间戳过期");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}boolean ifAbsent = stringContainer.setIfAbsent(MD5Utils.getDigest(userId + url + timestamp), "1", 61L);if(!ifAbsent){log.error("当前请求已被处理过,二次请求不再受理");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}}catch (Exception e1){try {dataObj = JSON.parseArray(plainText);}catch (Exception e2){dataObj = data + "";}}bodyJson.put("data",dataObj);requestWrapper.setRequestBody(bodyJson.toJSONString());}else if(data != null && !(data instanceof String)){log.error("服务已开启请求参数加密处理,但当前请求参数未被加密");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}}chain.doFilter(requestWrapper, responseWrapper);}else{chain.doFilter(request, responseWrapper);}// 拿到响应对象byte[] responseData = responseWrapper.getResponseData();String contentType = responseWrapper.getContentType();if(contentType != null && "application/json".equalsIgnoreCase(contentType.trim())){String originalResulet = new String(responseData);JSONObject jsonObject  =JSONObject.parseObject(originalResulet);// 拿到响应对象中的dataObject data = jsonObject.get("data");if(data != null){// AES 加密String k = StringUtils.getRandomString(16);String v = StringUtils.getRandomString(16);String encText = AesUtils.encrypt(JSON.toJSONString(data), AesUtils.Mode.API,k,v,codecProperties.isDbEnable());// s2 是服务端秘钥的摘要String s2 = ((HttpServletRequest) request).getHeader("s2");if( s2 == null){log.error("服务端秘钥的摘要缺失,需要添加请求头:s2 ");handlerExceptionResolver.resolveException((HttpServletRequest) request,(HttpServletResponse) response,null,new ParamCheckException("非法请求"));return;}String publicKey = (String)hashMapContainer.doQueryCache(CodecProperties.CLIENT_RSA_KEY_PRE + s2,RsaUtils.PUBLIC_KEY);if(publicKey == null){Map<String,String> clientRsa = rsaService.getRsa(CodecProperties.CLIENT_RSA_KEY_PRE,s2);publicKey = clientRsa.get(RsaUtils.PUBLIC_KEY);s2 = clientRsa.get(RsaUtils.SignMode.SHA1.getMode());}String aeskv = RsaUtils.publicEncrypt(k + "_" + v,publicKey);jsonObject.put("data", encText);jsonObject.put("u",aeskv);jsonObject.put("s2",s2);}responseData = jsonObject.toJSONString().getBytes("utf-8");}HttpServletResponse httpServletResponse = (HttpServletResponse) response;response.setContentLength(responseData.length);httpServletResponse.getOutputStream().write(responseData);httpServletResponse.getOutputStream().flush();}else{// 处理业务逻辑chain.doFilter(request, response);}}@Overridepublic void destroy() {}protected boolean isIgnore() {boolean isIgnore = false;String[] ignoreUrl = codecProperties.getIgnoreUrl();if (ArrayUtils.isNotEmpty(ignoreUrl)) {HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();for (String s : ignoreUrl) {if (PatternMatchUtils.simpleMatch(s, request.getRequestURI())) {isIgnore = true;break;}}}return isIgnore;}public static void main(String[] args) {int timestampObj = (int)230416900;long timestamp =timestampObj;}
}

秘钥的过期时间

     考虑到秘钥仍然有暴露的可能性,这两对秘钥会做定期的更新。前端加密所用私钥暴露的危害性很大,这对秘钥在后端生成后可定为 几分钟乃至一个小时过期时间;前端解密所用公钥暴露的危害性较小,这对秘钥在后端生成后可定为 一天的过期时间;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/409517.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2 种方式申请免费 SSL 证书,阿里云 Certbot

如何使用免费的 SSL 证书&#xff0c;有时在项目中需要使用免费的 SSL 证书&#xff0c;Aliyun 提供免费证书&#xff0c;三个月有效期&#xff0c;可以直接在aliyun 申请&#xff0c;搜索 SSL 证书&#xff0c;选择测试证书。 Aliyun 证书需要每三月来来换一次&#xff0c;页…

<数据集>车内视角行人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6470张 标注数量(xml文件个数)&#xff1a;6470 标注数量(txt文件个数)&#xff1a;6470 标注类别数&#xff1a;1 标注类别名称&#xff1a;[pedestrian] 序号类别名称图片数框数1pedestrian647029587 使用标注…

奔驰S迈巴赫S480升级动态按摩座椅效果怎么样

在迈巴赫 S480 的尊崇之旅中&#xff0c;舒适从未有尽头。现在&#xff0c;为您呈现前排动态按摩座椅的升级&#xff0c;将舒适体验提升至全新境界。 迈巴赫 S480 已然是舒适的代名词&#xff0c;但前排动态按摩座椅的升级&#xff0c;将为您带来前所未有的放松与享受。 当您…

网络UDP报文详细解析

目录 一、简介二、详细介绍三、其他相关链接1、TCP报文段的详细图总结2、TCP三次握手和四次挥手详解3、socket通信原理及相关函数详细总结4、网络包IP首部详细解析 一、简介 本文主要介绍UDP报文格式。 二、详细介绍 UDP是一种无连接、不可靠的用户数据报协议&#xff0c;其…

【注解】反序列化时匹配多个 JSON 属性名 @JsonAlias 详解

JsonAlias 注解是 Jackson 提供的一个功能强大的注解&#xff0c;允许一个字段在反序列化时匹配多个 JSON 属性名。它适用于在处理多种输入数据格式时&#xff0c;或当 JSON 数据的键名可能变化时。 一、JsonAlias 的作用 多种别名&#xff1a;JsonAlias 允许你为一个字段定义…

MySQL在Windows和Ubuntu上的安装与远程连接配置

MySQL是一个广泛使用的开源关系数据库管理系统&#xff0c;适用于各种操作系统。本文将详细介绍如何在Windows和Ubuntu系统上安装MySQL&#xff0c;并配置远程连接。 1. 在Windows上安装MySQL 1.1 下载MySQL安装包 首先&#xff0c;访问MySQL官方网站&#xff08;https://de…

UEFI 01记: 开发环境 在 ubuntu22 中搭建 edk2 开发环境并运行简单示例

https://uefi.org 1&#xff0c;预备环境 $ sudo apt install uuid-dev $ sudo apt install nasm $ sudo apt install bison flex $ sudo apt install build-essential $ sudo apt-get install x11proto-xext-dev $ sudo apt-get install libx11-dev $ sudo apt-get install l…

[数据集][目标检测]管道漏水泄漏破损检测数据集VOC+YOLO格式2614张4类

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2614 标注数量(xml文件个数)&#xff1a;2614 标注数量(txt文件个数)&#xff1a;2614 标注…

[windows][apache]Apache代理安装

下载apache服务软件和VC_redist安装包 https://www.apachelounge.com/download/ https://www.microsoft.com/zh-CN/download/details.aspx?id48145 解压文件&#xff0c;修改httpd.conf文件 37行出修改文件的解压目录 60行修改监听端口 安装服务 进入apache的目录&#xf…

美团mtgsig 1.2算法分析

声明 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。 本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。 如有侵权,请联系我进行删除。 这里只是我分析的分析过程,以及一些重要点的记录…

帮助我们从曲线图中获取数据的软件分享——GetData Graph Digitizer

在科技论文写作和数据分析过程中&#xff0c;我们常常需要将自己的数据与前人的研究成果进行对比。然而&#xff0c;有时我们只能从别人的论文中获得一张包含坐标轴的曲线图&#xff0c;而无法直接获取原始数据。在这种情况下&#xff0c;GetData Graph Digitizer 软件就显得尤…

人眼检测(单张图像)

生产资料私有化&#xff0c;是阻碍社会发展(包括学习)的一大阻力。希望更多学习资料公出供学习。 目录 实验原理 示例代码 运行结果 注意事项&#xff1a; 在OpenCV中使用C对中可以读取图像文件&#xff0c;检测图像中的人脸和眼睛&#xff0c;并在检测到的眼睛位置绘制矩…

Telnet详解与应用——从原理到实战模拟

1. 引言 在现代网络管理中&#xff0c;远程访问和控制设备的能力至关重要。Telnet是一种经典的远程访问协议&#xff0c;尽管在安全性方面逐渐被SSH等更现代化的协议取代&#xff0c;但其在早期网络管理中的广泛使用使其成为网络工程师的基本技能之一。本文将深入探讨Telnet的…

Leuze ROD4-20 ROD4-38系列激光扫描仪软件与操作手测

Leuze ROD4-20 ROD4-38系列激光扫描仪软件与操作手测

移动式气象站:科技赋能,监测天气

在自然灾害频发、气候变化日益显著的今天&#xff0c;准确、及时地获取气象信息对于农业生产、城市规划、交通运输以及灾害预警等领域至关重要。传统固定气象站虽能提供稳定的观测数据&#xff0c;但在偏远地区、灾害现场或快速变化的环境中&#xff0c;其局限性逐渐显现。为此…

数据仓库系列7:什么是概念模型、逻辑模型和物理模型,它们有什么区别?

你是否曾经困惑于数据仓库中的各种模型?概念模型、逻辑模型、物理模型 - 它们听起来很相似,但实际上各有千秋。 目录 引言:为什么模型如此重要?1. 概念模型:勾勒数据的蓝图什么是概念模型?概念模型的特点概念模型的例子概念模型的作用如何创建概念模型 2. 逻辑模型:细化你的…

十大护眼落地灯品牌哪个牌子好?落地灯品牌排行前十名

十大护眼落地灯品牌哪个牌子好&#xff1f;随着快经济时代的到来&#xff0c;人们在学业以及事业上的压力也日益增加&#xff0c;不少朋友反应在日常工作、学习是经常出现眼部疲劳的状况&#xff0c;甚至会时不时出现眼睛干涩、流泪&#xff0c;对学习、工作状态造成了极大的困…

【Python学习手册(第四版)】学习笔记20.2-迭代和解析(二)-迭代解析、迭代方法的计时比较、函数陷阱

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本文较简单&#xff0c;主要是概括了解析语法&#xff08;列表解析、生成器、集合、字典解析&#xff09;&#xff0c;以及对前面的各种迭代进行计时比较&#xf…

漂亮的业绩,重挫的股价,快手“遭遇”了什么?

现象与本质往往背道而驰&#xff0c;如同威廉莎士比亚形容的那般&#xff0c;“表面的平静可能掩盖了深深的波澜。”这一真理不仅充斥在生活中的各个片段&#xff0c;也散布在市场的每一个瞬间。 近日发布二季度财报的快手就迎来了“两级反转”。多项核心业绩指标录得增幅的同…

云计算产业链图谱_产业链全景图_云计算行业市场分析

在产业数字化转型的背景下&#xff0c;云计算作为信息技术的重要组成部分&#xff0c;正逐渐成为各行业数字化、智能化转型的关键支撑。受益于5G、大数据、物联网、人工智能等技术的快速发展&#xff0c;云计算产业规模持续扩大&#xff0c;市场需求不断增长。云计算作为一种新…