悦书阁 悦书阁
首页
学习笔记
技术文档
idea插件开发
更多
  • 分类
  • 标签
  • 归档

Felix

大道至简 知易行难
首页
学习笔记
技术文档
idea插件开发
更多
  • 分类
  • 标签
  • 归档
  • 部署文档

  • 常用手册

  • 经验技巧

    • stream使用
    • Oracle笔记
    • AES加解密
      • Git技巧备忘
      • Feign支持BasicAuth验证
      • Feign远程调用
      • Mybatis-xml语法备忘
      • 邮件发送自定义Excel
      • SpringBoot集成第三方组件
      • SpringBoot集成问题记录
      • mybaits plus 代码生成器
      • 阿尔萨斯(Arthas)
    • 技术文档
    • 经验技巧
    liufei379
    2022-06-10
    目录

    AES加解密

    # 1.时序图

    image-20220413104004740

    # 2.流程说明

    1.后端分配key
    2.浏览器发起请求时,获取当前时间戳作为盐值(salt)进行AES加密
    3.加密成AES后,需要再加密成BASE64传入后端,否则可能因为加密串的问题会丢失信息。
    4.同时需要把时间戳放入请求头(timestamp)
    5.然后后端获取消息体进行BASE64解密
    6.再获取请求头的timestamp+可以进行AES256解密
    7.处理完成之后根据请求头的timestamp进行AES加密和BASE64加密
    8.然后返回前端,前端再进行BASE64解密和AES解密
    
    $缺陷加解密耗时较长,大概2秒左右
    
    后续改成了无盐值模式,只是分配key,几十毫秒即可完成加解密。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 3.代码实现

    # 3.1加解密工具类
    import org.apache.commons.codec.binary.Base64;
    import javax.crypto.Cipher;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.UnsupportedEncodingException;
    import java.security.NoSuchAlgorithmException;
    
    public class AESEncrypt {
    
        public static final String UTF_8 = "UTF-8";
        private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
        private static final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        
        static {
            try {
                //预热,可以加快加解密速度
                Cipher.getInstance(ALGORITHM);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            }
        }    
    
        /**
         * 生成 SecretKey
         *
         * @param secret
         * @param salt
         * @return
         */
        public static SecretKey generateSecretKey(String secret, String salt) {
            SecretKey secretKey = null;
            try {
                //用盐值的模式,耗时将近2秒
                SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
                PBEKeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
                secretKey = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return secretKey;
        }
        
        /**
         * 生成 SecretKey 无salt
         *
         * @param secret
         * @return
         */
        private static SecretKey generateSecretKey(String secret) {
            return new SecretKeySpec(secret.getBytes(), "AES");
        }
    
        /**
         * AES256加密
         *
         * @param content
         * @param secretKey
         * @return
         */
        private static byte[] encryptAES(byte[] content, SecretKey secretKey) {
            byte[] str = null;
            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
                str = cipher.doFinal(content);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return str;
        }
        
        /**
         * AES256解密
         *
         * @param bytes
         * @param secretKey
         * @return
         */
        private static byte[] decryptAES(byte[] bytes, SecretKey secretKey) {
            byte[] decryptStr = null;
            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
                decryptStr = cipher.doFinal(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return decryptStr;
        }
    
        /**
         * BASE64加密
         *
         * @param content
         * @param secretKey
         * @return
         */
        private static String encrypt(String content, SecretKey secretKey) throws UnsupportedEncodingException {
            byte[] bytes = content.getBytes(UTF_8);
            return Base64.encodeBase64String(encryptAES(bytes, secretKey));
        }
     
        /**
         * BASE64加密
         *
         * @param content
         * @return
         */
        public static String encrypt(String content, String key) throws UnsupportedEncodingException {
            SecretKey secretKey = generateSecretKey(key);
            byte[] bytes = content.getBytes(UTF_8);
            return Base64.encodeBase64String(encryptAES(bytes, secretKey));
        }
    
        /**
         * BASE64解密
         *
         * @param content
         * @param secretKey
         * @return
         */
        private static String decrypt(String content, SecretKey secretKey) throws UnsupportedEncodingException {
            return new String(decryptAES(Base64.decodeBase64(content), secretKey), UTF_8);
        }
    
        /**
         * BASE64解密
         *
         * @param content
         * @return
         */
        public static String decrypt(String content, String key) throws UnsupportedEncodingException {
            SecretKey secretKey = generateSecretKey(key);
            return new String(decryptAES(Base64.decodeBase64(content), secretKey), UTF_8);
        }    
    
        public static void main(String[] args) {
            try {
                // generate secret key
                SecretKey secretKey = generateSecretKey("123456", "1234576");
    
                String context = "[{1122334455}]";
    
                String s = encrypt(context, secretKey);
                System.out.println(s);
    
                String s1 = decrypt(s, secretKey);
                System.out.println(s1);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    # 3.2 Filter拦截
    import cn.hutool.core.util.CharsetUtil;
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.extra.servlet.ServletUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.MediaType;
    import org.springframework.web.filter.GenericFilterBean;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebFilter(urlPatterns = "/api/*", filterName = "apiFilter")
    public class ApiFilter extends GenericFilterBean {
    
        /**
        配置类信息
    	@Value("${aes.key}")
    	private String key; //自定义key 分配给前端
    	@Value("${aes.open}")
    	private Boolean open; // 是否打开加密
    	**/
        @Autowired
        private AesConfig aesConfig;
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            ApiWrapper requestWrapper = null;
            if (request instanceof HttpServletRequest) {
    
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    
                if (!loginFilter(httpServletRequest, httpServletResponse)) {
                    return;
                }
    
                //是否开启加密
                if (!aesConfig.getOpen()) {
                    chain.doFilter(request, response);
                    return;
                }
    
                //只对POST请求做加解密 和前端约定
                if (ServletUtil.METHOD_POST.equals(httpServletRequest.getMethod())) {
                    try {
                        requestWrapper = new ApiWrapper((HttpServletRequest) request, aesConfig.getKey());
                    } catch (Exception e) {
                        throwFilterError(httpServletResponse, HttpStatus.PARAM_ERROR);
                        return;
                    }
                }
            }
            chain.doFilter(requestWrapper, response);
        }
    
        /**
         * 参数解密失败
         *
         * @param response
         */
        private void throwFilterError(HttpServletResponse response, HttpStatus status) throws IOException {
    
            Result result = new Result(status);
    
            response.setCharacterEncoding(CharsetUtil.UTF_8);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setStatus(200);
            PrintWriter printWriter = response.getWriter();
            printWriter.append(Json.toJsonString(result));
    
        }
    
        /**
         * 登录拦截
         *
         * @param request
         */
        private Boolean loginFilter(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
            //不拦截/order/share
            if (request.getRequestURI().contains("share")) {
                return true;
            }
            //分享登录校验
            if (request.getRequestURI().contains("inviteDeclaration")
                    || request.getRequestURI().contains("checkDeclaration")
                    || request.getRequestURI().contains("groupDeclaration")) {
                String token = request.getHeader("token");
                Object userInfo = RedisUtil.get(RedisKey.API_SHARE_TOKEN + token);
                if (ObjectUtil.isNull(userInfo)) {
                    throwFilterError(response, HttpStatus.LINK_EXPIRED);
                    return false;
                }
                ApiUserUtil.putUser(Json.parseObject(userInfo.toString(), ApiUser.class));
            } else {
                //普通登录校验
                if (!request.getRequestURI().contains("login") && !request.getRequestURI().contains("gestureLogin")) {
                    String token = request.getHeader("token");
                    Object userInfo = RedisUtil.get(RedisKey.API_TOKEN + token);
                    if (ObjectUtil.isNull(userInfo)) {
                        throwFilterError(response, HttpStatus.UNAUTHORIZED);
                        return false;
                    }
                    RedisUtil.set(RedisKey.API_TOKEN + token, userInfo, 7 * 24 * 24 * 60);
                    ApiUserUtil.putUser(Json.parseObject(userInfo.toString(), ApiUser.class));
                }
            }
            return true;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    # 3.3自定义Filter拦截类
    import org.apache.commons.io.IOUtils;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    public class ApiWrapper extends HttpServletRequestWrapper {
    
        private byte[] requestBody = null;//用于将流保存下来
    
        public ApiWrapper(HttpServletRequest request, String key) throws IOException {
            super(request);
            requestBody = IOUtils.toByteArray(request.getInputStream());
            String salt = request.getHeader("timestamp");
            //解密参数
            requestBody = AESEncrypt.decrypt(new String(requestBody, StandardCharsets.UTF_8), key, salt).getBytes(StandardCharsets.UTF_8);
        }
    
        @Override
        public ServletInputStream getInputStream() {
            // 返回的是我们处理之后的数据
            final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
            return new ServletInputStream() {
                @Override
                public int read() {
                    return bais.read();  // 读取 requestBody 中的数据
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
                }
            };
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    # 3.4返回参数AOP方式加密

    也可以使用ResponseBodyAdvice方式

    import cn.hutool.core.util.ObjectUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Component
    @Aspect
    @Slf4j
    public class AESAspect {
    
        @Autowired
        private AesConfig aesConfig;
    
        private final HttpServletRequest request;
    
        public AESAspect(HttpServletRequest request) {
            this.request = request;
        }
    
        @Pointcut(value = "execution(* com.felix.api.controller..*.*(..))")
        public void encrypt() {
    
        }
    
        /**
         * 参数加解密
         *
         * @param joinPoint
         */
        @Around(value = "encrypt()")
        public Result encryptParam(ProceedingJoinPoint joinPoint) {
    
            try {
                String salt = request.getHeader("timestamp");
                Result result = (Result) joinPoint.proceed();
                Object data = result.getData();
                if (ObjectUtil.isNull(data)) {
                    return result;
                }
                if (!aesConfig.getOpen()) {
                    return result;
                }
                result.setData(AESEncrypt.encrypt(Json.toJsonString(data), aesConfig.getKey(), salt));
                return result;
            } catch (ServiceException e) {
                throw new ServiceException(e.getMessage());
            } catch (Throwable e) {
                log.info(e.getMessage());
                throw new ServiceException("参数转换失败");
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    上次更新: 2026/3/11 21:47:04
    Oracle笔记
    Git技巧备忘

    ← Oracle笔记 Git技巧备忘→

    最近更新
    01
    实现idea开发的关键步骤
    10-05
    02
    Redis高可用架构
    09-09
    03
    Zookeeper高可用
    08-31
    更多文章>
    Theme by Vdoing | Copyright © 2022-2026 Felix | 粤ICP备17101757号-1
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式