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

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

前言

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

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

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

介绍

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

对称加密算法

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

非对称加密算法

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

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

区别

密钥

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

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

速度

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

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

安全性

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

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

实现

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

引入依赖

xml 复制代码
<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位的

java 复制代码
/**
 * @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工具类

java 复制代码
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);
    }
}

自定义注解

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

java 复制代码
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

java 复制代码
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

java 复制代码
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;
    }
}

编写测试控制器

java 复制代码
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 接口返回的密文作为参数传进去。

测试

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

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

感谢观看。


推荐

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

Bummon's BlogBummon's Home公众号

相关推荐
云烟成雨TD6 小时前
Spring AI Alibaba 1.x 系列【69】Token 用量统计
java·人工智能·spring
JAVA9656 小时前
JAVA面试-并发篇 03-使用synchronized doublecheck实现单例有什么坑
java·单例模式·面试
在繁华处6 小时前
Java从零到熟练(四):面向对象基础
java·开发语言
JustHappy8 小时前
古法编程秘籍(二):什么是代码模块化?别背概念,把房间收拾明白就够了
前端·后端
小江的记录本8 小时前
【JVM虚拟机】堆内存分代模型:年轻代(Eden+Survivor)、老年代、元空间Metaspace(附《思维导图》+《面试高频考点清单》)
java·前端·jvm·后端·python·spring·面试
在繁华处8 小时前
Java从零到熟练(三):流程控制
java·开发语言·python
唐青枫8 小时前
Java Optional 实战指南:优雅处理空值与链式转换
java
一起学开源8 小时前
一文读懂 ReAct 范式:让 AI Agent 真正学会“思考+行动“
java·javascript·react.js·ecmascript·react·alibaba·智能体开发
逍遥德9 小时前
MQTT教程详解-04.SpringBoot集成MQTT(告别手动控制)
java·spring boot·物联网·中间件·iot·iotdb
语戚10 小时前
力扣 3161. 块放置查询:线段树解法(Java 实现)
java·算法·leetcode·面试·线段树·力扣·