前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于SpringBoot的AES加密算法接口处理

基于SpringBoot的AES加密算法接口处理

作者头像
关忆北.
发布2021-12-07 16:36:43
3K0
发布2021-12-07 16:36:43
举报
文章被收录于专栏:关忆北.

Advanced Encryption Standard

Advanced Encryption Standard缩写:AES,译为高级加密标准。

AES是用于取代DES的对称加密算法,既然有对称加密,那么会有非对称加密,常见的非对称加密有RSA加密。

何谓对称和非对称?

对称加密即为只有一个公钥,数据加密者和数据解密者共有一个公钥,可使用公钥完成数据的加密和解密,密钥由双方商定共同保管。而非对称加密的密钥可分为公钥和私钥,私钥用于数据的加密,公钥用于数据的解密,公私钥的其中一方无法完成数据的加密和解密,且加密后的数据无法被反解密。

因此,对于安全性而言,显而易见的是非对称加密更加安全,但对称加密效率更高。

本篇文章的主要内容是AES对称加密。

AES加密过程

前置条件:

  • 明文P,待加密数据
  • 密钥K,分组密码,每16字节一个分组,用于设定加密轮数
  • AES加密函数(E)
  • AES解密函数 (D)
  • 密文C,经密钥K加密后的明文

设加密函数为E,则有

​ C = E(K,P)

代码语言:javascript
复制
所以,密文是由明文P作为入参经过密钥K加密特定轮数得到。

设解密函数为D,则有

​ P = D (K, C)

​ 所以,密文解密是由密文C和密钥K作为解密入参经解密函数得到。

AES密钥可由Hex生成

代码语言:javascript
复制
// 使用密钥生成器 KeyGenerator 生成的 16 字节随机密钥的 hex 字符串,使用时解 hex 得到二进制密钥
byte[] bytes = AesEncryptUtil.initKey();

String aesKey = Hex.encodeHexString(bytes);

SpringBoot整合AES加密步骤

  1. AesEncryptUtils
  2. 自定义注解
  3. 全局统一数据处理
  4. Controller中使用注解

SpringBoot整合AES加密

AesEncryptUtil.class

代码语言:javascript
复制
/**
 * AES 加/解密工具类
 * 使用密钥时请使用 initKey() 方法来生成随机密钥
 * initKey 方法内部使用 java.crypto.KeyGenerator 密钥生成器来生成特定于 AES 算法参数集的随机密钥
 */
public class AesEncryptUtil {

    private AesEncryptUtil() {
    }

    /**
     * 密钥算法类型
     */
    public static final String KEY_ALGORITHM = "AES";

    /**
     * 密钥的默认位长度
     */
    public static final int DEFAULT_KEY_SIZE = 128;

    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding";
    public static final String ECB_NO_PADDING = "AES/ECB/NoPadding";

    public static String base64Encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    public static byte[] base64Decode(String base64Code) {
        return Base64.decodeBase64(base64Code);
    }

    public static byte[] aesEncryptToBytes(String content, String hexAesKey) throws Exception {
        return encrypt(content.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(hexAesKey.toCharArray()));
    }

    public static String aesEncrypt(String content, String hexAesKey) throws Exception {
        return base64Encode(aesEncryptToBytes(content, hexAesKey));
    }

    public static String aesDecryptByBytes(byte[] encryptBytes, String hexAesKey) throws Exception {
        byte[] decrypt = decrypt(encryptBytes, Hex.decodeHex(hexAesKey.toCharArray()));
        return new String(decrypt, StandardCharsets.UTF_8);
    }

    public static String aesDecrypt(String encryptStr, String hexAesKey) throws Exception {
        return aesDecryptByBytes(base64Decode(encryptStr), hexAesKey);
    }


    /**
     * 生成 Hex 格式默认长度的随机密钥
     * 字符串长度为 32,解二进制后为 16 个字节
     *
     * @return String Hex 格式的随机密钥
     */
    public static String initHexKey() {
        return Hex.encodeHexString(initKey());
    }

    /**
     * 生成默认长度的随机密钥
     * 默认长度为 128
     *
     * @return byte[] 二进制密钥
     */
    public static byte[] initKey() {
        return initKey(DEFAULT_KEY_SIZE);
    }

    /**
     * 生成密钥
     * 128、192、256 可选
     *
     * @param keySize 密钥长度
     * @return byte[] 二进制密钥
     */
    public static byte[] initKey(int keySize) {
        // AES 要求密钥长度为 128 位、192 位或 256 位
        if (keySize != 128 && keySize != 192 && keySize != 256) {
            throw new RuntimeException("error keySize: " + keySize);
        }
        // 实例化
        KeyGenerator keyGenerator;
        try {
            keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("no such algorithm exception: " + KEY_ALGORITHM, e);
        }
        keyGenerator.init(keySize);
        // 生成秘密密钥
        SecretKey secretKey = keyGenerator.generateKey();
        // 获得密钥的二进制编码形式
        return secretKey.getEncoded();
    }

    /**
     * 转换密钥
     *
     * @param key 二进制密钥
     * @return Key 密钥
     */
    private static Key toKey(byte[] key) {
        // 实例化 DES 密钥材料
        return new SecretKeySpec(key, KEY_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data 待加密数据
     * @param key  密钥
     * @return byte[] 加密的数据
     */
    public static byte[] encrypt(byte[] data, byte[] key) {
        return encrypt(data, key, ECB_PKCS_5_PADDING);
    }

    /**
     * 加密
     *
     * @param data            待加密数据
     * @param key             密钥
     * @param cipherAlgorithm 算法/工作模式/填充模式
     * @return byte[] 加密的数据
     */
    public static byte[] encrypt(byte[] data, byte[] key, final String cipherAlgorithm) {
        // 还原密钥
        Key k = toKey(key);
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            // 初始化,设置为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, k);

            // 发现使用 NoPadding 时,使用 ZeroPadding 填充
            if (ECB_NO_PADDING.equals(cipherAlgorithm)) {
                return cipher.doFinal(formatWithZeroPadding(data, cipher.getBlockSize()));
            }

            // 执行操作
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("AES encrypt error", e);
        }
    }

    /**
     * 解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密的数据
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        return decrypt(data, key, ECB_PKCS_5_PADDING);
    }

    /**
     * 解密
     *
     * @param data            待解密数据
     * @param key             密钥
     * @param cipherAlgorithm 算法/工作模式/填充模式
     * @return byte[] 解密的数据
     */
    public static byte[] decrypt(byte[] data, byte[] key, final String cipherAlgorithm) {
        // 还原密钥
        Key k = toKey(key);
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            // 初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, k);

            // 发现使用 NoPadding 时,使用 ZeroPadding 填充
            if (ECB_NO_PADDING.equals(cipherAlgorithm)) {
                return removeZeroPadding(cipher.doFinal(data), cipher.getBlockSize());
            }

            // 执行操作
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("AES decrypt error", e);
        }
    }

    private static byte[] formatWithZeroPadding(byte[] data, final int blockSize) {
        final int length = data.length;
        final int remainLength = length % blockSize;

        if (remainLength > 0) {
            byte[] inputData = new byte[length + blockSize - remainLength];
            System.arraycopy(data, 0, inputData, 0, length);
            return inputData;
        }
        return data;
    }

    private static byte[] removeZeroPadding(byte[] data, final int blockSize) {
        final int length = data.length;
        final int remainLength = length % blockSize;
        if (remainLength == 0) {
            // 解码后的数据正好是块大小的整数倍,说明可能存在补 0 的情况,去掉末尾所有的 0
            int i = length - 1;
            while (i >= 0 && 0 == data[i]) {
                i--;
            }
            byte[] outputData = new byte[i + 1];
            System.arraycopy(data, 0, outputData, 0, outputData.length);
            return outputData;
        }
        return data;
    }

    public static void main(String[] args) throws Exception {

        byte[] bytes = AesEncryptUtil.initKey();

        // 使用密钥生成器 KeyGenerator 生成的 16 字节随机密钥的 hex 字符串,使用时解 hex 得到二进制密钥
        String aesKey = Hex.encodeHexString(bytes);
        System.out.println(aesKey);

//        String aesKey = "d86d7bab3d6ac01ad9dc6a897652f2d2";

        String content = "你好";
        System.out.println("加密前:" + content);

        String encrypt = aesEncrypt(content, aesKey);
        System.out.println(encrypt.length() + ":加密后:" + encrypt);

        String decrypt = aesDecrypt(encrypt, aesKey);
        System.out.println("解密后:" + decrypt);
    }

}
编写自定义注解

SecurityParameter.class

代码语言:javascript
复制
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {
    /**
     * 入参是否解密,默认解密
     * @return
     */
    boolean inDecode() default true;

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

加解密请求体处理

定义全局处理: 请求:拿密文来请求(request)到接口时,我们先在请求到接口前做一步解密处理,即:DecodeRequestBodyAdvice 返回:接口数据在Controller返回之后,在请求体中(Response)对数据先进行加密处理用于脱敏,即 EncodeResponseBodyAdvice

DecodeRequestBodyAdvice.class

代码语言:javascript
复制
@Slf4j
@ControllerAdvice(basePackages = "com.ltx.aes_demo.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) {
        try {
            boolean encode = false;
            if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(SecurityParameter.class)) {
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                encode = serializedField.inDecode();
            }
            if (encode) {
                log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密");
                return new MyHttpInputMessage(inputMessage);
            } else {
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("对方法method :【" + Objects.requireNonNull(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();
            this.body = IOUtils.toInputStream(AesEncryptUtil.aesDecrypt(easpString(IOUtils.toString(inputMessage.getBody(), StandardCharsets.UTF_8)), AesConstant.aesKey));
        }

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

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


        public String easpString(String requestData) {
            if (StringUtils.isNotBlank(requestData)) {
                String s = "{\"requestData\":";
                if (!requestData.startsWith(s)) {
                    throw new RuntimeException("参数【requestData】缺失异常!");
                } else {
                    int closeLen = requestData.length() - 1;
                    int openLen = "{\"requestData\":".length();
                    return StringUtils.substring(requestData, openLen, closeLen);
                }
            }
            return "";
        }
    }
}

请求的json格式为: {“requestData”:“0Wa795xrq/8Gp9pUKZcYgEOPreQvHXZJYhWoNH9bbuSfbTDjb65VdzHZEsx3FhRiysxU04KACg1gXFBla4WBggj5u4EjmVfUP3hotGOeY+M=”}

EncodeResponseBodyAdvice.class

代码语言:javascript
复制
@Slf4j
@ControllerAdvice(basePackages = "com.ltx.aes_demo.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 (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(SecurityParameter.class)) {
            //获取注解配置的包含和去除字段
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出参是否需要加密
            encode = serializedField.outEncode();

            /**
             * 测试自定义注解,学习AES加解密可忽略删除此if
             */
            if (Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(Decrypt.class)){
                log.info("own @interface");
                Decrypt decryptField = methodParameter.getMethodAnnotation(Decrypt.class);
                boolean test = decryptField.test();
                if (test){
                    log.info("奥里给!");
                }
            }
        }
        if (encode) {
            log.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return AesEncryptUtil.aesEncrypt(result, AesConstant.aesKey);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }
}
测试
代码语言:javascript
复制
    @SecurityParameter
    @GetMapping("getUser")
    public User getUser() {
        return userService.getUserById();
    }

    @Decrypt
    @SecurityParameter
    @PostMapping("test")
    public String test(@RequestBody User user) {
        return "hello world";
    }


    @PostMapping("/save")
    @ResponseBody
    @SecurityParameter(outEncode = false)
    public User save(@RequestBody User info) {
        System.out.println(info);
        return info;
    }

写在最后

如果不明白自定义注解的使用请查看

Spring自定义注解常用元素

如果不明白全局统一异常处理请查看

ResponseBody、RequestBodyAdvice解读

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Advanced Encryption Standard
    • 何谓对称和非对称?
      • AES加密过程
      • SpringBoot整合AES加密
        • 编写自定义注解
          • 测试
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档