教你如何使用AES对接口参数进行加密

news2024/11/24 12:39:02

教你如何使用AES对接口参数进行加密

前言

我们作为程序猿,在浏览网站的时候偶尔也会打开控制台看看请求的接口,我们会发现有些接口的传输是 “乱码” ,那么这个乱码究竟是什么呢?为什么要这么做?

其实这个所谓的 “乱码” 其实是一种加密后的密文,其原理是前后端提前约定好一种协议,在该协议下进行加解密的处理,例如:前端将数据加密后发送给后端,后端接收到参数后,第一时间先在约定好的协议下将密文解密成可识别的对象,再进行逻辑处理,最后将结果加密返回给前端,前端获取到密文后,同样依照约定好的协议对密文进行解密,最后将解密出来的数据拿来使用。

那么我们想实现同样的效果,应该如何做呢?别急,听哥们给你一一道来。

介绍

一般来说加密算法会分为两种:对称加密算法非对称加密算法

对称加密算法

摘自百度百科: 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

非对称加密算法

摘自百度百科: 不对称加密算法使用两把完全不同但又是完全匹配的一对钥匙—公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

经过百度百科中的简单概要,我们已经知道了对称加密算法非对称加密算法 都是什么,但它们中间又有什么不同呢?早就猜到你会这么问了,所以我已经把它们的区别一一列出来了。

区别

密钥

对称加密: 一共只有一种密钥,并将该密钥同时用来加解密。

非对称加密: 共有两种密钥:公钥私钥 ,使用公钥来加密,使用私钥来解密。

速度

对称加密: 算法简单且加解密容易,所以执行效率高、速度快。

非对称加密: 由于加密算法比较复杂,所以加解密的效率很低,速度远不如 对称加密

安全性

对称加密: 由于加解密均使用的为同一个密钥,那么若密钥泄露则有被破解密文的风险。

非对称加密: 由于使用了两种密钥,且公钥是可公开的密钥,使用私钥来进行解密,消除了用户交换密钥的条件,极大程度上保证了数据安全。

实现

在这里给大家介绍一下 AES + CBC + PKCS5Padding 的加密方式,具体实现如下:

引入依赖

<dependency>
            <groupId>org.apache.directory.studio</groupId>
            <artifactId>org.apache.commons.codec</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.13.0</version>
        </dependency>

编写密钥荷载

注意:这里的 AES_KEYAES_IV 可以自定义,但 必须是16位的

/**
 * @author Bummon
 * @description 荷载
 * @date 2023-08-12 10:27
 */
public class Common {

    /**
     * AES密钥
     */
    public static final byte[] AES_KEY = "Ct9x5IUNHlhq0siZ".getBytes();

    /**
     * AES偏移
     */
    public static final byte[] AES_IV = "MIIBIjANBgkqhkiG".getBytes();

}

编写AES工具类

import com.test.constant.Common;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * @author Bummon
 * @description AES工具类
 * @date 2023-08-12 09:26
 */
public class AESUtils {

    private static final String ALGORITHMSTR = "AES/CBC/PKCS5Padding";

    /**
     * @param content 加密内容
     * @return {@link String}
     * @date 2023-08-12 09:27
     * @author Bummon
     * @description 加密
     */
    public static String encrypt(String content) {
        String encode = null;
        try {
            Cipher cipher = initCipher(Cipher.ENCRYPT_MODE);
            byte[] encodeBytes = cipher.doFinal(content.getBytes());
            encode = Base64.encodeBase64String(encodeBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encode;
    }

    public static String decrypt(String encryptStr) {
        String decode = null;
        try {
            Cipher cipher = initCipher(Cipher.DECRYPT_MODE);
            byte[] encodeBytes = Base64.decodeBase64(encryptStr);
            byte[] decodeBytes = cipher.doFinal(encodeBytes);
            decode = new String(decodeBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decode;
    }

    /**
     * @param cipherMode 操作类型 加密/解密
     * @return {@link Cipher}
     * @date 2023-08-12 09:42
     * @author Bummon
     * @description 初始化Cipher
     */
    private static Cipher initCipher(int cipherMode) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        SecretKeySpec keySpec = new SecretKeySpec(Common.AES_KEY, "AES");
        IvParameterSpec ivParam = new IvParameterSpec(Common.AES_IV);
        cipher.init(cipherMode, keySpec, ivParam);
        return cipher;
    }

    public static void main(String[] args) {
        String encrypt = AESUtils.encrypt("Hello World");
        String decrypt = AESUtils.decrypt(encrypt);
        System.out.println(encrypt);
        System.out.println(decrypt);
    }
}

自定义注解

该注解作用于接口上,可以对接口的加密或者解密实现更加粒子化的控制,默认入参解密,出参加密。

import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;

/**
 * @author Bummon
 * @description AES加解密注解
 * @date 2023-08-12 09:44
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface AES {

    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;
}

DecodeRequestBodyAdvice

import com.test.anno.AES;
import com.test.util.pwd.AESUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

/**
 * @author Bummon
 * @date 2023-08-12 10:22
 * @description 请求数据解密
 */
@Slf4j
@ControllerAdvice(basePackages = "com.test.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        try {
            boolean encode = false;
            if (methodParameter.getMethod().isAnnotationPresent(AES.class)) {
                //获取注解配置的包含和去除字段
                AES aes = methodParameter.getMethodAnnotation(AES.class);
                //入参是否需要解密
                encode = aes.decode();
            }
            if (encode) {
                log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密");
                return new MyHttpInputMessage(inputMessage);
            } else {
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            return inputMessage;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class MyHttpInputMessage implements HttpInputMessage {
        private HttpHeaders headers;

        private InputStream body;

        public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
            this.headers = inputMessage.getHeaders();
            String param = IOUtils.toString(inputMessage.getBody(), "UTF-8");
            //去除请求数据中的转义字符
            String encryptStr = easpString(param).replace("\"", "");
            String decrypt = AESUtils.decrypt(encryptStr);
            this.body = IOUtils.toInputStream(decrypt, "UTF-8");
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        /**
         * @param param
         * @return
         */
        public String easpString(String param) {
            if (param != null && !param.equals("")) {
                String s = "{\"param\":";
                //去除param中的转义字符
                String data = param.replaceAll("\\s*|\r|\n|\t", "");
                if (!data.startsWith(s)) {
                    throw new RuntimeException("参数【param】缺失异常!");
                } else {
                    int closeLen = data.length() - 1;
                    int openLen = "{\"param\":".length();
                    String substring = StringUtils.substring(data, openLen, closeLen);
                    return substring;
                }
            }
            return "";
        }
    }
}

EncodeResponseBodyAdvice

import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.anno.AES;
import com.test.util.pwd.AESUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
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.ResponseBodyAdvice;

/**
 * @author Bummon
 * @date 2023-08-12 10:36
 * @description 返回参数加密
 */
@Slf4j
@ControllerAdvice(basePackages = "com.test.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {

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

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter methodParameter,
                                  MediaType mediaType,
                                  Class aClass,
                                  ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        boolean encode = false;
        if (methodParameter.getMethod().isAnnotationPresent(AES.class)) {
            //获取注解配置的包含和去除字段
            AES aes = methodParameter.getMethodAnnotation(AES.class);
            //出参是否需要加密
            encode = aes.encode();
        }
        if (encode) {
            log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return AESUtils.encrypt(result);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }
}

编写测试控制器

import java.util.HashMap;
import java.util.Map;

/**
 * @author Bummon
 * @description
 * @date 2023-08-12 10:37
 */
@RestController
public class TestController {

    @AES(decode = false)
    @GetMapping("/getSecret")
    public Object getSecret() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "Bummon");
        map.put("homeUrl", "https://www.bummon.com/");
        map.put("blogUrl", "https://blog.bummon.com/");
        return map;
    }

    @AES(encode = false)
    @PostMapping("/getBySecret")
    public Object getBySecret(@RequestBody Map<String, Object> map) {
        return map;
    }
}

我们在这里编写了两个接口,其中 getSecret 接口不对入参进行解密,对出参进行加密,也就是前端传明文,后端返回为密文。getBySecret 接口是对入参进行解密,不对出参加密,也就是前端传密文,后端返回为明文。

我们的测试思路就是先测试getSecret 接口,同时也获取到了密文,在测试getBySecret 接口时将getSecret 接口返回的密文作为参数传进去。

测试

image.png

我们通过getSecret 接口拿到了密文,接下来将该密文作为参数调用getBySecret 接口。

image.png

可以看到我们成功将密文解析了出来,并且对接口入参没有影响。

感谢观看。


推荐

关注博客和公众号获取最新文章

Bummon’s Blog | Bummon’s Home | 公众号

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无涯教程-Perl - qq函数

描述 可以使用此函数代替双引号。这实际上不是一个函数,更像是一个运算符,但是如果您在其他程序员的程序中看到它却不记得它是什么,那么可能会在这里看。实际上,您可以使用任何一组定界符,而不仅仅是括号。 语法 以下是此函数的简单语法- qq ( string )返回值 该函数返回双…

De Bruijin序列与魔术(三)——De Bruijin序列的拓展思考

早点关注我&#xff0c;精彩不错过&#xff01; 在前面的文章中&#xff0c;我们已经介绍完经典DeBruijin序列的原理和魔术&#xff0c;相关内容请戳&#xff1a; De Bruijin序列与魔术&#xff08;二&#xff09;——魔术《De Bruijin序列》 De Bruijin序列与魔术&#xff08;…

Chord diagram | 啧啧啧!~人人必会的Chord diagram你不来学一学吗!?

1写在前面 啊啊啊啊啊&#xff01;&#xff01;&#xff01;&#xff01;~终于值完夜班休息了。&#x1f62d; 最近是大搞医疗反腐的日子&#xff0c;㊗️各位执法人员成绩满满&#xff01;~&#x1f912; 听说以后医务人员要年薪制了&#xff0c;完全搞不懂这些东西的初衷和理…

七夕好物分享,哪些礼物适合送男/女朋友?这几款好物最为合适!

七夕是个值得纪念的日子&#xff0c;牛郎织女鹊桥相会的故事百年流传&#xff0c;七夕是一个表达爱意的节日&#xff0c;送礼物是必不可少的&#xff0c;情侣们可以选择一份有意义的礼物&#xff0c;也可以选择对方需要的东西当做礼物来赠送&#xff0c;总的来说&#xff0c;送…

STM32F429IGT6使用CubeMX配置按键检测

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置GPIO引脚 4、生成工程配置 5、部分代码 /* USER CODE BEGIN 0 */ //按键检测函数 void KEY_Test(void) {if(SET HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)){while(SET HAL_GPIO_ReadPin(…

HCIP 链路聚合技术

1、链路聚合概述 为了保证网络的稳定性&#xff0c;仅仅是设备进行备份还不够&#xff0c;我们需要针对我们的链路进行备份&#xff0c;同时也增加了链路的利用率&#xff0c;提高带宽。避免一条链路出现故障&#xff0c;导致网络无法正常通信。这就可以使用链路聚合技术。 以…

PyTorch翻译官网教程-NLP FROM SCRATCH: GENERATING NAMES WITH A CHARACTER-LEVEL RNN

官网链接 NLP From Scratch: Generating Names with a Character-Level RNN — PyTorch Tutorials 2.0.1cu117 documentation 使用字符级RNN生成名字 这是我们关于“NLP From Scratch”的三篇教程中的第二篇。在第一个教程中</intermediate/char_rnn_classification_tutor…

走进知识图谱(二)【世界知识图谱篇】知识表示的经典模型与平移模型及基于复杂关系建模的知识表示学习

上篇文章提到&#xff0c;该系列文章将主要围绕世界知识图谱和语言知识图谱这两大类知识图谱进行展开&#xff0c;并且提到知识图谱的主要研究包括了知识表示学习、知识自动获取和知识的推理与应用三大部分。今天主要介绍世界知识图谱的知识表示学习&#xff0c;其中包括经典的…

netty基础与原理

Netty线程模型和Reactor模式 简介&#xff1a;reactor模式 和 Netty线程模型 设计模式——Reactor模式&#xff08;反应器设计模式&#xff09;&#xff0c;是一种基于 事件驱动的设计模式&#xff0c;在事件驱动的应用中&#xff0c;将一个或多个客户的 服务请求分离&#x…

跑步运动耳机哪个牌子好、跑步运动耳机推荐

随着生活质量的提高&#xff0c;运动健身已经成为一种新的潮流&#xff0c;而跑步更是如今众多人参与的热门运动项目之一。在谈到跑步时&#xff0c;很多人习惯性地戴上耳机&#xff0c;在奔跑的过程中播放自己喜欢的音乐&#xff0c;以免运动变得枯燥&#xff0c;并增添一些轻…

开工大吉|华润鞋业二期自动化改造项目开工典礼圆满举行

2023年8月10日上午&#xff0c;山东百华鞋业有限公司择良辰吉时隆重举行了华润鞋业二期厂房动工仪式&#xff0c;公司总经理郭兴梅女士携公司管理层代表和施工单位代表参加了动工仪式。 根据公司发展规划&#xff0c;对未来发展的美好期许&#xff0c;以及公司生产与研发保持的…

从 Zebec Protocol 长期布局看,ZBC 通证的潜在应用场景

流支付协议 Zebec Protocol 在去年被推出以来&#xff0c;始终保持着较为迅猛的市场进展&#xff0c;与此同时其还基于 ZBC 构建了全新的治理体系&#xff0c;并上线了以 ZBC 资产为核心的治理系统&#xff0c;让生态逐渐走向 DAO。

题目大解析(3)

题目 字符串中的第一个唯一字符 字符串中的第一个唯一字符 原题链接&#xff1a;字符串中的第一个唯一字符 计数法&#xff1a; class Solution { public:int firstUniqChar(string s) {int arr[130] {0};for(auto x : s){arr[x-0];}int i 0;for(auto x : s){if(arr[x-0] …

《算法竞赛·快冲300题》每日一题:“造电梯”

《算法竞赛快冲300题》将于2024年出版&#xff0c;是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码&#xff0c;以中低档题为主&#xff0c;适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 造…

使用python对图像加噪声

加上雨点噪声 import cv2 import numpy as npdef get_noise(img, value10):#生成噪声图像>>> 输入&#xff1a; img图像value 大小控制雨滴的多少 >>> 返回图像大小的模糊噪声图像noise np.random.uniform(0, 256, img.shape[0:2])# 控制噪声水平&#xff…

Arduino ESP32 v2 使用记录:开发环境搭建

文章目录 目的开发环境搭建程序下载测试使用VS Code进行开发批量烧录固件到模块中总结 目的 在之前的文章 《使用Arduino开发ESP32&#xff08;01&#xff09;&#xff1a;开发环境搭建》 中介绍了使用Arduino开发ESP32的开发环境搭建内容&#xff0c;只不过当时的 Arduino co…

写一个函数返回参数二进制中 1 的个数(c语言三种实现方法)

&#xff08;本文旨在自己做题时的总结&#xff0c;我会给出不同的解法&#xff0c;后面如果碰到新的题目还会加入其中&#xff0c;等于是我自己的题库。 1.写一个函数返回参数二进制中 1 的个数。 比如&#xff1a; 15 0000 1111 4 个 1 方法一&#xff1a; #include…

Kubernetes 调度约束(亲和性、污点、容忍)

目录 一、Pod启动典型创建过程 二、调度流程 三、指定调度节点 1.使用nodeName字段指定调度节点 2.使用nodeSelector指定调度节点 2.1给对应的node节点添加标签 2.2修改为nodeSelector调度方式 3.通过亲和性来指定调度节点 3.1节点亲和性 3.2Pod亲和性与反亲和性 3.2…

SAP MM学习笔记18- SQVI 工具

Tr-cd SQVI 是一个SAP数据库工具。 使用这个工具&#xff0c;可以自己构建查询界面中的条件&#xff0c;查询对象&#xff0c;从而自由查询数据&#xff0c;实现标准没有的功能。 1&#xff0c;SQVI 和 SQ1&#xff0c;SQ2&#xff0c;SQ3 的不同 SQ1&#xff0c;2&#xff0…

【hello C++】智能指针

目录 一、内存泄漏 1.1 什么是内存泄漏&#xff0c;内存泄漏的危害 1.2 内存泄漏分类 1.3 如何检测内存泄漏 1.4 如何避免内存泄漏 二、智能指针的使用及原理 2.1 RAII 2.2 智能指针的原理 2.3 智能指针的发展历程 2.4 智能指针的模拟及实现 三、shared_ptr 常见的问题 3.1 线程…