还不会参数加密?快来学学AES加密的方法

前言

原文链接:教你如何使用 AES 加密接口参数

在实际工作中为了安全,会将参数进行密文传输,这里使用的是 AES + ECB + BASE64 对称加密的加密方式,在此记录并学习一下,技术不精望大家谅解。

介绍

AES加密是一种对称加密,有对称加密自然也有非对称加密

对称加密
对称加密是一种简单快速的加密方式,它在加密和解密的时候使用的密钥是同一个,并且通常不会超过256bit,密钥越小,在加解密的时候耗时越短,但是同样的,对于数据的加密会稍弱一些。本文使用的加密方式正是对称加密

非对称加密
非对称加密提供了一种更为安全的加解密解决方案,它使用了一对密钥,也就是一个公钥和一个私钥。在加密的时候使用公钥加密,且公钥可以发给任何请求的人,而解密的时候则需要使用私钥去解密。但是相应的,加解密的速度会稍慢。

总结来说:
对称加密的速度更快,但密钥传输相对麻烦,且加密稍弱,有泄露风险;
非对称加密的安全性更高,加密方式较强,密钥传输简单,但加密速度较慢,几乎没有泄露风险,适合偶尔传输数据的请求。

Start

引入依赖

使用之前要先引入此依赖

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

编写AES加解密工具类

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

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.apache.commons.codec.binary.Base64;

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

public class AesEncryptUtils {
    //密钥 16位 可自定义
    //注意:此处的密钥要与前端一致,否则会导致解密失败
    private static final String KEY = "Mh82Pw93Q0ePaz5";

    //算法名称/加密模式/数据填充方式
    //注意: 加密方式与数据填充方式要与前端对应,否则会加密失败
    //这里使用的是ECB加密方式,数据填充使用的是PKCS5
    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";

    /**
     * 加密
     * @param content 加密的字符串
     * @param encryptKey key值
     * @return
     * @throws Exception
     */
    public static String encrypt(String content, String encryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
        byte[] b = cipher.doFinal(content.getBytes("utf-8"));
        // 采用base64算法进行转码,避免出现中文乱码
        return Base64.encodeBase64String(b);

    }

    /**
     * 解密
     * @param encryptStr 解密的字符串
     * @param decryptKey 解密的key值
     * @return
     * @throws Exception
     */
    public static String decrypt(String encryptStr, String decryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        // 根据算法名称/加密方式/填充方式初始化加密
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
        // 采用base64算法进行转码,避免出现中文乱码
        byte[] encryptBytes = Base64.decodeBase64(encryptStr);
        byte[] decryptBytes = cipher.doFinal(encryptBytes);
        return new String(decryptBytes);
    }

    public static String encrypt(String content) throws Exception {
        return encrypt(content, KEY);
    }
    public static String decrypt(String encryptStr) throws Exception {
        return decrypt(encryptStr, KEY);
    }
}

以上则是AES用来加解密的工具类,可写个单元测试或写个main方法来测试是否可以正常加解密成功。

自定义注解

该注解可用于方法上,对需要加密的接口进行粒子化控制

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

/**
 * 出入参加解密
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface AES {

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

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

编写请求数据解密 ControllerAdvice

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * 请求数据解密
 */
@ControllerAdvice(basePackages = "com.xxx.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {

    private static final Logger logger = LoggerFactory.getLogger(DecodeRequestBodyAdvice.class);

    @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(SecurityParameter.class)) {
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                encode = serializedField.inDecode();
            }
            if (encode) {
                logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密");
                return new MyHttpInputMessage(inputMessage);
            } else {
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.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 requestData = IOUtils.toString(inputMessage.getBody(), "UTF-8");
            //去除请求数据中的转义字符
            String encryptStr = easpString(requestData).replace("\"", "");
            String decrypt = AesEncryptUtils.decrypt(encryptStr);
            this.body = IOUtils.toInputStream(decrypt, "UTF-8");
        }

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

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

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

编写返回数据加密 ControllerAdvice

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * 返回数据加密
 */
@ControllerAdvice(basePackages = "com.xxx.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {

    private final static Logger logger = LoggerFactory.getLogger(EncodeResponseBodyAdvice.class);

    @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(SecurityParameter.class)) {
            //获取注解配置的包含和去除字段
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出参是否需要加密
            encode = serializedField.outEncode();
        }
        if (encode) {
            logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return AesEncryptUtils.encrypt(result);
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }
}

测试

此时我们已经编写完所有代码,我们来写一个测试类来测试加解密是否正常

interface-test
我们写了两个接口,getSecret是获取返回内容的密文,getBySecret是解析密文
此时我们使用postman来测试一下接口

getSecret

postman-getSecret
可以看到我们可以正常获取到密文,那我们使用这个密文来调用另外一个接口看是否能正确解析出密文内容

getBySecret

postman-getBySecret
可以看到密文已经被成功解析出来了,同时也是我们加密前的内容。
自此我们的加解密就全部结束啦,如果想要出参加密、入参解密的话直接加上注解,不带属性即可

注意:此处后端使用的是加密名为AES,加密方式为ECB,数据填充格式为PKCS5Padding。前后端一定要完全一致,否则会导致前后端解密失败

图表

为了便于大家理解,在这里做了两个图表参考

时序图

前端 后端 DecodeRequestBodyAdvice 业务处理 EncodeResponseBodyAdvice 发送加密数据 解密 解密后的参数 加密返回信息 返回加密信息 前端 后端 DecodeRequestBodyAdvice 业务处理 EncodeResponseBodyAdvice

流程图

发送加密信息
解密
加密
前端
后端
是否开启解密
DecodeRequestBodyAdvice
业务处理
是否开启加密
EncodeResponseBodyAdvice

推荐

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

Bummon’s BlogBummon’s Home公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bummon.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值