首页 > 其他分享 >怎样来实现数据接口的加密?

怎样来实现数据接口的加密?

时间:2022-12-14 18:33:45浏览次数:48  
标签:return String 接口 springframework org import 怎样 public 加密


前言

特别是在做一下政府项目时,有时候需要对所有接口数据进行加密。

加密传输结构体定义

  • 请求头
localhost:8080/user/info
header:
Content-Type:application/json
signature:55efb04a83ca083dd1e6003cde127c45
timestamp:1648308048
salt:123456
clientType:ANDORID
  • body体
// 原始请求体
{
"page": 1,
"size": 10
}
// 加密后的请求体
{
"data": "1ZBecdnDuMocxAiW9UtBrJzlvVbueP9K0MsIxQccmU3OPG92oRinVm0GxBwdlXXJ"
}

// 加密响应体:
{
"data": "fxHYvnIE54eAXDbErdrDryEsIYNvsOOkyEKYB1iBcre/QU1wMowHE2BNX/je6OP3NlsCtAeDqcp7J1N332el8q2FokixLvdxAPyW5Un9JiT0LQ3MB8p+nN23pTSIvh9VS92lCA8KULWg2nViSFL5X1VwKrF0K/dcVVZnpw5h227UywP6ezSHjHdA+Q0eKZFGTEv3IzNXWqq/otx5fl1gKQ==",
"code": 200,
"signature": "aa61f19da0eb5d99f13c145a40a7746b",
"msg": "",
"timestamp": 1648480034,
"salt": 632648
}
// 解密后的响应体:
{
"code": 200,
"data": [{
"id": 1,
"name": "boyka",
"registerTime": "2022-03-27T00:19:43.699",
"userType": "COMMON"
}],
"msg": "用户列表查询成功",
"salt": 0
}

AES加密工具类

import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

/**
*/
public class EncryptUtils {

private EncryptUtils() {

}

private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public static final String IV = "2233445566778899";

/**
* base 64 encode
*
* @param bytes 待编码的byte[]
* @return 编码后的base 64 code
*/
public static String base64Encode(byte[] bytes) {
String data = Base64.encodeBase64String(bytes);
if (!StringUtils.isEmpty(data)) {
// 处理一行超过76个字符换行问题
return data.replaceAll("[\\s*\t\n\r]", "");
} else {
return data;
}
}

/**
* base 64 decode
*
* @param base64Code 待解码的base 64 code
* @return 解码后的byte[]
* @throws Exception
*/
public static byte[] base64Decode(String base64Code) throws Exception {
return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code);
}

/**
* AES加密
*
* @param content 待加密的内容
* @param encryptKey 加密密钥
* @return 加密后的byte[]
* @throws Exception
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
byte[] raw = encryptKey.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec(IV.getBytes());//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
return cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
}


/**
* AES解密
*
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密密钥
* @return 解密后的String
* @throws Exception
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
byte[] raw = decryptKey.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec(IV.getBytes());//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}


/**
* AES加密为base 64 code
*
* @param content 待加密的内容
* @param encryptKey 加密密钥
* @return 加密后的base 64 code
* @throws Exception
*/
public static String aesEncrypt(String content, String encryptKey) throws Exception {
return base64Encode(aesEncryptToBytes(content, encryptKey));
}

/**
* 将base 64 code AES解密
*
* @param encryptStr 待解密的base 64 code
* @param decryptKey 解密密钥
* @return 解密后的string
* @throws Exception
*/
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
}

public static void main(String[] args) throws Exception {
String timestamp = "1648308048";
String salt = "123456";
String data = "{\n" +
"\t\"page\": 1,\n" +
"\t\"size\": 10\n" +
"}";
String privateKey = "8VBkO5amPcPrXV3m";
String encryptData = aesEncrypt(data, privateKey);
System.out.println(encryptData);
encryptData = "fxHYvnIE54eAXDbErdrDryEsIYNvsOOkyEKYB1iBcre/QU1wMowHE2BNX/je6OP3NlsCtAeDqcp7J1N332el8r4JPvZmKlXKUUReD8ayUckrFQkSATLwASJmMTArIeit28+LbLFz9e9K0vdaRi/splThh1+7u5CDg/ixDWwVThOlZknRgP3RKar6KN7BJGFyCJYNqpYPkuapsa2B2B6DHQ==";
String decryptData = aesDecrypt(encryptData, privateKey);
System.out.println(decryptData);
String signature = Md5Utils.genSignature(timestamp + salt + encryptData + privateKey);
System.out.println(signature);
}

public static byte[] generateDesKey(int length) throws Exception {
//实例化
KeyGenerator kgen = KeyGenerator.getInstance("AES");
//设置密钥长度
kgen.init(length);
//生成密钥
SecretKey skey = kgen.generateKey();
//返回密钥的二进制编码
return skey.getEncoded();
}

public static int genSalt() {
int salt = 0;
while (true) {
salt = (int) (Math.random() * 1000000);
if (salt < 1000000 && salt > 99999)
break;
}
return salt;
}
}

Md5Utils.java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public class Md5Utils {
/**
* 生成签名
*
* @param value 数据
* @return 结果
*/
public static String genSignature(String value) {
try {
// 得到一个信息摘要器
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] result = digest.digest(value.getBytes());
StringBuilder buffer = new StringBuilder();
// 把每个byte 做一个与运算 0xff;
for (byte b : result) {
// 与运算
int number = b & 0xff;// 加盐
String str = Integer.toHexString(number);
if (str.length() == 1) {
buffer.append("0");
}
buffer.append(str);
}
// 标准的md5加密后的结果
return buffer.toString();
} catch (NoSuchAlgorithmException e) {
return "";
}
}
}

加密拦截器与过滤

SecretFilter

import org.springframework.beans.factory.annotation.Value;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
* @description: 过滤器
*/
public class SecretFilter implements Filter {
public static ThreadLocal<Boolean> secretThreadLocal = new ThreadLocal<>();
@Value("${secret.privateKey.h5}")
private String h5Key;

@Value("${secret.privateKey.default}")
private String defaultKey;

public static ThreadLocal<String> clientPrivateKeyThreadLocal = new ThreadLocal<>();

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 免加密
if (!request.getRequestURI().startsWith(SecretConstant.PREFIX)) {
secretThreadLocal.set(Boolean.FALSE);
filterChain.doFilter(request, response);
} else {
// 加密,先只考虑POST情况
secretThreadLocal.set(Boolean.TRUE);
// 简单获取对应加密的私钥
String privateKey = ("H5".equalsIgnoreCase(Objects.requireNonNull(request.getHeader("clientType")))) ? h5Key : defaultKey;
clientPrivateKeyThreadLocal.set(privateKey);
filterChain.doFilter(request, response);
}
}
}
  • 请求参数处理类
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Objects;

import static cn.boykaff.encrypt.common.base.ResponseCode.SECRET_API_ERROR;

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class SecretRequestAdvice extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
//如果支持加密消息,进行消息解密。
String httpBody;
if (Boolean.TRUE.equals(SecretFilter.secretThreadLocal.get())) {
httpBody = decryptBody(inputMessage);
} else {
httpBody = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset());
}
//返回处理后的消息体给messageConvert
return new SecretHttpMessage(new ByteArrayInputStream(httpBody.getBytes()), inputMessage.getHeaders());
}

/**
* 解密消息体
*
* @param inputMessage 消息体
* @return 明文
*/
private String decryptBody(HttpInputMessage inputMessage) throws IOException {
InputStream encryptStream = inputMessage.getBody();
String requestBody = StreamUtils.copyToString(encryptStream, Charset.defaultCharset());
// 验签过程
HttpHeaders headers = inputMessage.getHeaders();
if (CollectionUtils.isEmpty(headers.get("clientType"))
|| CollectionUtils.isEmpty(headers.get("timestamp"))
|| CollectionUtils.isEmpty(headers.get("salt"))
|| CollectionUtils.isEmpty(headers.get("signature"))) {
throw new ResultException(SECRET_API_ERROR, "请求解密参数错误,clientType、timestamp、salt、signature等参数传递是否正确传递");
}

String timestamp = String.valueOf(Objects.requireNonNull(headers.get("timestamp")).get(0));
String salt = String.valueOf(Objects.requireNonNull(headers.get("salt")).get(0));
String signature = String.valueOf(Objects.requireNonNull(headers.get("signature")).get(0));
String privateKey = SecretFilter.clientPrivateKeyThreadLocal.get();
ReqSecret reqSecret = JSON.parseObject(requestBody, ReqSecret.class);
String data = reqSecret.getData();
String newSignature = "";
if (!StringUtils.isEmpty(privateKey)) {
newSignature = Md5Utils.genSignature(timestamp + salt + data + privateKey);
}
if (!newSignature.equals(signature)) {
// 验签失败
throw new ResultException(SECRET_API_ERROR, "验签失败,请确认加密方式是否正确");
}

try {
String decrypt = EncryptUtils.aesDecrypt(data, privateKey);
if (StringUtils.isEmpty(decrypt)) {
decrypt = "{}";
}
return decrypt;
} catch (Exception e) {
log.error("error: ", e);
}
throw new ResultException(SECRET_API_ERROR, "解密失败");
}
}
  • 相应参数处理类
    SecretResponseAdvice
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import static cn.boykaff.encrypt.common.base.ResponseCode.SECRET_API_ERROR;

/**
* @description:
* @author: boykaff
* @date: 2022-03-25-0025
*/
@ControllerAdvice
public class SecretResponseAdvice implements ResponseBodyAdvice {
private Logger logger = LoggerFactory.getLogger(SecretResponseAdvice.class);

@Autowired
private ObjectMapper objectMapper;

@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}

@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 判断是否需要加密
Boolean respSecret = SecretFilter.secretThreadLocal.get();
String secretKey = SecretFilter.clientPrivateKeyThreadLocal.get();
// 清理本地缓存
SecretFilter.secretThreadLocal.remove();
SecretFilter.clientPrivateKeyThreadLocal.remove();
if (null != respSecret && respSecret) {
if (o instanceof ResponseBasic) {
// 外层加密级异常
if (SECRET_API_ERROR == ((ResponseBasic) o).getCode()) {
return SecretResponseBasic.fail(((ResponseBasic) o).getCode(), ((ResponseBasic) o).getData(), ((ResponseBasic) o).getMsg());
}
// 业务逻辑
try {
// 使用FastJson序列号会导致和之前的接口响应参数不一致,后面会重点讲到
String data = EncryptUtils.aesEncrypt(objectMapper.writeValueAsString(o), secretKey);
// 增加签名
long timestamp = System.currentTimeMillis() / 1000;
int salt = EncryptUtils.genSalt();
String dataNew = timestamp + "" + salt + "" + data + secretKey;
String newSignature = Md5Utils.genSignature(dataNew);
return SecretResponseBasic.success(data, timestamp, salt, newSignature);
} catch (Exception e) {
logger.error("beforeBodyWrite error:", e);
return SecretResponseBasic.fail(SECRET_API_ERROR, "", "服务端处理结果数据异常");
}
}
}
return o;
}
}

过滤器配置

@Configuration
public class WebConfig {
@Bean
public Filter secretFilter() {
return new SecretFilter();
}


@Bean
public FilterRegistrationBean filterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new DelegatingFilterProxy("secretFilter"));
registration.setName("secretFilter");
registration.addUrlPatterns(PREFIX + "/*");
registration.setOrder(1);
return registration;
}
}

总结

好了到这里大的骨架就已经有了,这样就完成了加解密的过程。
客户端请求加密->发起请求->服务端解密->业务处理->服务端响应加密->客户端解密展示


标签:return,String,接口,springframework,org,import,怎样,public,加密
From: https://blog.51cto.com/u_15461374/5938171

相关文章

  • mysql 实现加密搜索
    前言:我们经常遇到由于安全考虑,数据库中一些用户信息比如手机号、身份证号等信息加密保存,但又有一些需要查询匹配的操作,比如手机号模糊匹配的场景,此时就无法直接使用模糊查询......
  • Azure 解决方案:如何为Azure VM配置加密策略
    51CTO博客地址:​​https://blog.51cto.com/14669127​​Azure培训视频地址:​​https://space.bilibili.com/2000820534​​为了避免我们使用云端资源数据泄露,我们也需要考......
  • 【FFH】OpenHarmony北向-Full SDK的获取与安装(使用系统接口)
    前言在我们开发一些应用能力并查阅文档中各式各类的API时,有时会看到很多不错的能力,却因为是系统能力而无法使用,在这里给出自己在开发的过程中的一些经验,解决因为没有FullS......
  • 接口设计 多字段表格 排序 查询Token
    www.tapd.cn   每个字段都支持排序https://www.tapd.cn/api/basic/token/generate_token_from_array{  "workspace_id":"33345311",  "data":{ ......
  • Java8:Lambdas(二)学习怎样去使用lambda表达式
    JavaSE8的发布很快就到了。伴随着它来的不仅仅是新的语言lambda表达式(同样被称为闭包或匿名方法)——伴随着一些语言特性支持——更重要的是API和library的增强将会使传统......
  • 搭建网关微服务实现接口统一访问
    我们现在搭建一个Zuul网关,实现在第9章创建的商品和订单两个微服务的接口通过网关统一访问。同样,先创建一个SpringBoot项目,命名为zuul,如图所示。  然后,在“Dependenc......
  • Raw NAND FLASH原理及ONFI接口标准【转】
    转自:https://blog.csdn.net/ScilogyHunter/article/details/105995767一、NANDFlash类型1.1NANDFlash的两大分类NANDFlash是嵌入式世界里常见的存储器,对于嵌入式开发......
  • 关于.net6.0中swagger偶尔无法加载接口的问题笔记
    有时候在修改接口或者其它代码时运行发现swagger会加载不了接口列表,有时前端也无法调用接口,我遇到过两三次这样的问题了,一般swagger加载不了接口列表,如下图所示: 找不到......
  • CTF常见的加密和编码方法
    目录​​哈希摘要算法​​​​对称加密算法​​​​其他加密算法​​​​编码​​哈希摘要算法以 root 加密为例。MD4:32位的摘要算法。2add09183d0b1dc0428701df9838fbaM......
  • Basler相机C语言接口说明
    由于公司项目需要,使用了Basler相机。所以特写此文记录如何使用Basler相机进行采图。公司项目用的代码是不能贴出来的,所以在这篇文章里就不贴代码了,只做流程上的说明。本文......