注解实现前后端交互数据的加密和解密

390 阅读8分钟

注解实现前后端交互数据的加密和解密

在实际开发中,用户的一些形如密码、手机号等敏感信息需要进行安全传输和保密,从而保护用户的个人数据安全。比如密文存储、敏感数据加密传输等。本文以注解的方式,实现对数据的加密和加密。 代码结构如下:

cipher
├─ annoation
│  ├─ CipherDecrypt.java
│  ├─ CipherEncrypt.java
│  └─ CipherField.java
├─ CipherAbstract.java
├─ CipherAspect.java
├─ constant
│  └─ CipherRange.java
└─ impl
   ├─ CipherStringByBase64.java
   ├─ CipherStringByRsa.java
   └─ CipherStringByTest.jav

一、公共类

  • CipherRange
/**
 * @author 摆渡人
 * @description 密文处理范围
 */
public enum CipherRange {
    /**
     * 只针对请求的数据,进行加密或解密
     */
    REQUEST,
    /**
     * 只针对响应的数据。进行加密或解密
     */
    RESPONSE
}

这个可以定义加密注解或解密注解是在请求时还是在响应时触发。

二、定义声明注解

  • CipherDecrypt

声明对数据进行解密,需要定义解密的实现算法。

import java.lang.annotation.*;

/**
 * @author 摆渡人
 * @description 密文解密声明
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherDecrypt {

    /**
     * 解密的操作范围
     * @return CipherRange
     */
    CipherRange cipherRange() default CipherRange.RESPONSE;

    /**
     * 加密或解密的实现算法,优先级低于@CipherField
     * @return Class<? extends CipherAbstract>
     */
    Class<? extends CipherAbstract> using();

}
  • CipherEncrypt

声明对数据进行加密,需要定义加密的实现算法。

import java.lang.annotation.*;

/**
 * @author 摆渡人
 * @description 密文加密声明
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherEncrypt {

    /**
     * 加密的操作范围
     * @return CipherRange
     */
    CipherRange cipherRange() default CipherRange.REQUEST;

    /**
     * 加密或解密的实现算法,优先级低于@CipherField
     * @return Class<? extends CipherAbstract>
     */
    Class<? extends CipherAbstract> using();
    
}
  • CipherField

声明对哪些字段进行密文操作。

import java.lang.annotation.*;

/**
 *
 * 只对基本类型或String生效
 * 若是一个集合、或一个对象,需在该字段是加上此注解
 * 此时,对象或集合的每一个对象,会找到有此注解的基本类型或string,并进行加密或解密操作
 *
 * @author 摆渡人
 * @description 密文字段声明
 * @date 2023/6/29 16:04
 */
@Target({ElementType.FIELD, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CipherField {

    /**
     * 加密或解密的实现算法
     * 优先级: CipherField.using() > CipherDecrypt.using() = CipherEncrypt.using()
     * @return Class<? extends CipherAbstract>
     */
    Class<? extends CipherAbstract> using() default CipherAbstract.None.class;
}

三、抽象类CipherAbstract<T>

实现该抽象类,从而自定义加密、解密算法

/**
 * @author 摆渡人
 * @description 抽象类,自定义密文算法。
 */
public abstract class CipherAbstract<T> {

    public CipherAbstract() {
    }

    /**
     * 加密
     * @param value 原数据
     * @return 加密后的数据
     */
    public abstract T encrypt(T value);

    /**
     * 解密
     * @param value 原数据
     * @return 解密后的数据
     */
    public abstract T decrypt(T value);

    /**
     * 默认,不加密/不解密,直接返回原数据
     */
    public abstract static class None extends CipherAbstract<Object> {
        private None() {
        }
    }
}

四、切面CipherAspect

/**
 * @author 摆渡人
 * @description 密文切面
 */
@Order(-100)
@Aspect
@Component
@Slf4j
public class CipherAspect {

    /**
     * 加密切点
     */
    @Pointcut("@annotation(cipher.annoation.CipherEncrypt)")
    public void encryptPointCut() {}

    /**
     * 解密切点
     */
    @Pointcut("@annotation(cipher.annoation.CipherDecrypt)")
    public void decryptPointCut() {}

    /**
     * 加密切点执行
     * @param point 切点
     * @param encrypt 加密注解
     * @return Object
     */
    @Around("encryptPointCut() && @annotation(encrypt)")
    public Object encryptAround(ProceedingJoinPoint point, CipherEncrypt encrypt) {
        Object response = null;
        CipherRange cipherRange = encrypt.cipherRange();
        try {
            if(CipherRange.REQUEST.equals(cipherRange)) {
                // 对请求数据进行加密
                Object[] args = point.getArgs();
                if(args != null && args.length != 0) {
                    Arrays.stream(args).forEach(f -> cipherObject(f,true,encrypt.using()));
                }
            }
            response = point.proceed();
            if(CipherRange.RESPONSE.equals(cipherRange)) {
                // 对响应数据进行加密
                cipherObject(response,true,encrypt.using());
            }
        } catch (Throwable e) {
            return ResponseDTO.userErrorParam("加密失败[{}]".replaceAll("\\{}",e.getMessage()));
        }
        return response;
    }

    /**
     * 解密切点执行
     * @param point 切点
     * @param decrypt 解密注解
     * @return Object
     */
    @Around("decryptPointCut() && @annotation(decrypt)")
    public Object decryptAround(ProceedingJoinPoint point, CipherDecrypt decrypt) {
        Object response = null;
        CipherRange cipherRange = decrypt.cipherRange();
        try {
            if(CipherRange.REQUEST.equals(cipherRange)) {
                // 对请求数据进行解密
                Object[] args = point.getArgs();
                if(args != null && args.length != 0) {
                    Arrays.stream(args).forEach(f -> cipherObject(f,false,decrypt.using()));
                }
            }
            response = point.proceed();
            if(CipherRange.RESPONSE.equals(cipherRange)) {
                // 对响应数据进行解密
                cipherObject(response,false,decrypt.using());
            }
        } catch (Throwable e) {
            return ResponseDTO.userErrorParam("解密失败[{}]".replaceAll("\\{}",e.getMessage()));
        }
        return response;
    }

    /**
     * 对单个object进行密文操作
     * @param object 数据
     * @param encryptFlag true:加密 false:解密
     * @param fatherCipher CipherAbstract<T>的实现类
     */
    private void cipherObject(Object object,boolean encryptFlag,Class<? extends CipherAbstract> fatherCipher) {
        if(object == null) {
            return;
        }

        Field[] fields = object.getClass().getDeclaredFields();
        //遍历字段,找出需要解密的字段
        Arrays.stream(fields).filter(field -> field != null && field.isAnnotationPresent(CipherField.class)).forEach(field -> {
            // accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的)
            field.setAccessible(true);

            try {
                // 获取字段值明文
                Object plaintext = field.get(object);

                if(plaintext == null) {
                    return;
                }

                // 符合条件,进行密文操作
                if(isPrimitive(plaintext)) {
                    CipherField cipherField = field.getAnnotation(CipherField.class);
                    Class<? extends CipherAbstract> sonAbstract = cipherField.using();
                    if(sonAbstract == CipherAbstract.None.class){
                        if(fatherCipher == CipherAbstract.None.class) {
                            return;
                        }
                        CipherAbstract fatherInstance = fatherCipher.newInstance();
                        Object ciphertext = encryptFlag ? fatherInstance.encrypt(plaintext) : fatherInstance.decrypt(plaintext);
                        field.set(object,ciphertext);
                        return;
                    }
                    CipherAbstract sonInstance = sonAbstract.newInstance();
                    Object ciphertext = encryptFlag ? sonInstance.encrypt(plaintext) : sonInstance.decrypt(plaintext);
                    field.set(object,ciphertext);
                    return;
                }

                // 获取密文,并重新赋值
                if(plaintext instanceof Collection) {
                    // 情况1:集合
                    Collection<?> collection = (Collection<?>) plaintext;
                    collection.forEach(f -> cipherObject(f,encryptFlag,fatherCipher));
                } else if (plaintext instanceof Array) {
                    // 情况2:数组
                    int length = Array.getLength(plaintext);
                    for(int i = 0; i < length; i ++) {
                        Object arrayValue = Array.get(plaintext, i);
                        cipherObject(arrayValue,encryptFlag,fatherCipher);
                    }
                } else if(plaintext instanceof Map) {
                    // 情况三:map
                    Map<?,?> map = (Map<?, ?>) plaintext;
                    map.forEach((k,v) -> cipherObject(v,encryptFlag,fatherCipher));
                } else {
                    // 情况五: 对象
                    cipherObject(plaintext,encryptFlag,fatherCipher);
                }

            } catch (Exception e) {
                log.error("对单个object进行密文操作发生异常[{}]",e.getMessage());
                throw new RuntimeException(e.getMessage());
            }
        });
    }

    /**
     * 判断是否是基本数据类型或String类型
     * @param obj
     * @return
     */
    public boolean isPrimitive(Object obj) {
        return obj instanceof Byte ||
                obj instanceof Short ||
                obj instanceof Integer ||
                obj instanceof Long ||
                obj instanceof Float ||
                obj instanceof Double ||
                obj instanceof Character ||
                obj instanceof Boolean ||
                obj instanceof String;
    }

}

千万不要忘了修改这两个地方:

/**
 * 加密切点
 */
@Pointcut("@annotation(net.lab1024.sa.common.module.support.cipher.annoation.CipherEncrypt)")
public void encryptPointCut() {}

/**
 * 解密切点
 */
@Pointcut("@annotation(net.lab1024.sa.common.module.support.cipher.annoation.CipherDecrypt)")
public void decryptPointCut() {}

五、实现样例:RSA

源码参考RSA 加解密(Java 实现)

/**
 * @author 摆渡人
 * @description 基于rsa算法对String类型的进行密文操作
 * @date 2023/7/5 17:56
 */
@Slf4j
public class CipherStringByRsa extends CipherAbstract<String> {

    private final String PRIVATE = "";//私钥
    private final String PUBLIC = "";//公钥

    @Override
    public String encrypt(String value) {
        return encrypt(value,PUBLIC);
    }

    @Override
    public String decrypt(String value) {
        return decrypt(value,PRIVATE);
    }

    public static final String KEY_ALGORITHM = "RSA";

    private static final String PUBLIC_KEY = "RSAPublicKey";

    private static final String PRIVATE_KEY = "RSAPrivateKey";

    // 1024 bits 的 RSA 密钥对,最大加密明文大小
    private static final int MAX_ENCRYPT_BLOCK = 117;

    // 1024 bits 的 RSA 密钥对,最大解密密文大小
    private static final int MAX_DECRYPT_BLOCK = 128;

    // 生成密钥对
    public Map<String, Object> initKey(int keysize) throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        // 设置密钥对的 bit 数,越大越安全
        keyPairGen.initialize(keysize);
        KeyPair keyPair = keyPairGen.generateKeyPair();

        // 获取公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    // 获取公钥字符串
    public String getPublicKeyStr(Map<String, Object> keyMap) {
        // 获得 map 中的公钥对象,转为 key 对象
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        // 编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    // 获取私钥字符串
    public String getPrivateKeyStr(Map<String, Object> keyMap) {
        // 获得 map 中的私钥对象,转为 key 对象
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        // 编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    // 获取公钥
    public PublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] publicKeyByte = Base64.getDecoder().decode(publicKeyString);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByte);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }

    // 获取私钥
    public PrivateKey getPrivateKey(String privateKeyString) throws Exception {
        byte[] privateKeyByte = Base64.getDecoder().decode(privateKeyString);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * BASE64 编码返回加密字符串
     *
     * @param key 需要编码的字节数组
     * @return 编码后的字符串
     */
    public String encryptBASE64(byte[] key) {
        return new String(Base64.getEncoder().encode(key));
    }

    /**
     * BASE64 解码,返回字节数组
     *
     * @param key 待解码的字符串
     * @return 解码后的字节数组
     */
    public byte[] decryptBASE64(String key) {
        return Base64.getDecoder().decode(key);
    }

    /**
     * 公钥加密
     *
     * @param text         待加密的明文字符串
     * @param publicKeyStr 公钥
     * @return 加密后的密文
     */
    public String encrypt(String text, String publicKeyStr) {
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));
            byte[] tempBytes = cipher.doFinal(text.getBytes("UTF-8"));
            return Base64.getEncoder().encodeToString(tempBytes);
        } catch (Exception e) {
            log.error("公钥加密失败[{}]",e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 私钥解密
     *
     * @param secretText    待解密的密文字符串
     * @param privateKeyStr 私钥
     * @return 解密后的明文
     */
    public String decrypt(String secretText, String privateKeyStr) {
        try {
            // 生成私钥
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));
            // 密文解码
            byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes("UTF-8"));
            byte[] tempBytes = cipher.doFinal(secretTextDecoded);
            return new String(tempBytes);
        } catch (Exception e) {
            log.error("私钥解密失败[{}]",e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

}

这里填入生成的公钥和密钥。公钥交由前端,前端根据需求,对特定字段的数据,利用公钥进行密文操作。密钥保存在服务端,且千万不可泄漏。

private final String PRIVATE =
private final String PUBLIC = 

测试案例

1.定义接口
@CipherDecrypt(cipherRange = CipherRange.REQUEST,using = CipherStringByRsa.class)
@NoNeedLogin
@PostMapping("/login")
@ApiOperation("登录")
public ResponseDTO<LoginEmployeeDetail> login(@Valid @RequestBody LoginForm loginForm) {
    log.info("收到的数据体:{}",loginForm);
    return ResponseDTO.ok();
}
@Data
public class LoginForm {

    @ApiModelProperty("登录名")
    @NotBlank(message = "登录名不能为空")
    @Length(max = 1024, message = "登录账号最多1024字符")
    @CipherField
    private String loginName;

    @ApiModelProperty("密码")
    @NotBlank(message = "密码不能为空")
    @CipherField
    private String password;
}
2.生成密钥
@Test
public void initRsa() throws Exception{
    CipherStringByRsa cipherStringByRsa = new CipherStringByRsa();
    Map<String, Object> stringObjectMap = cipherStringByRsa.initKey(1024);
    log.info("公钥:{}",cipherStringByRsa.getPublicKeyStr(stringObjectMap));
    log.info("私钥:{}",cipherStringByRsa.getPrivateKeyStr(stringObjectMap));
}
[INFO ][main] 公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZpJ+rK23/Ys3GWfRQS3RFvioGFFn6RFsMw1FEY3xsv6Y9lTIhSty/CP154CfO90N4bmIDijkAHu/kmCHcGfJdfCys3MH+VlptVeCuLUOOu0H14jek+NuUurxY5jFT4vYGF3nx4x0skLCbOyfPhnnQNfqSN26DpmKJKT/GIkrQPwIDAQAB (CipherTest.java:26)
[INFO ][main] 私钥:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANmkn6srbf9izcZZ9FBLdEW+KgYUWfpEWwzDUURjfGy/pj2VMiFK3L8I/XngJ873Q3huYgOKOQAe7+SYIdwZ8l18LKzcwf5WWm1V4K4tQ467QfXiN6T425S6vFjmMVPi9gYXefHjHSyQsJs7J8+GedA1+pI3boOmYokpP8YiStA/AgMBAAECgYARC9jJokwqPYXabD87br17nOpTsZJejK4I8N9TmOss5VHS7vWPL/rcJ4sa2ZikmC87poGcv125ibLGV7gHMi0OSmlJ2tk2vbGfCIYf1cKwUQiQRfvoqHjcUMvWunCtH2OouNrpZ2wUTv8YMWUB4QiGlntov/xZPvHOInYgeyMw4QJBAPBHHUDklYfM32ljQfrqFYs62WKnws1KNKWxDm5jDE67zqGW3XSC8yj+kxwx9UQAtFPsF+xrPW6b0oIYxbbok48CQQDn4lsV2gnaQU/GOqyWxLKBht8gcccerLdtdj48L9qNdJzglZ0Rg76ptSoPcJPJXEBKSsNWeo9RTxA2U2RNeeBRAkEA1rsnKJxgCfoM2zlKePwD/ua76c/1ZtPfUwh11i8Mi7yJwJ18EikJOGSygesfvwNGJQ4En1ODljsJmRycbYdSfwJBANwBo7Ij4V02wl2tpbqWfVbvhBvgV6/+5znwCjRIou+7SGy9Bv1rJicWQAXOJinSYR0buiH+vqPCg/tHO89WZ0ECQQC50PRcMBpW7FLOMMgt0kRvhOs/Cdfw6IiLwYrSG42vTFUb7jH5QPpaC/n4lb0CthORoQJdEm61tA7rdjlY8rH3 (CipherTest.java:27)

3.公钥加密

我们假设账号密码为:

{
    "loginName": "loginName",
    "password": "password"
}

经过加密后可得到:

@Test
public void testEncrypt() {
    CipherStringByRsa cipherStringByRsa = new CipherStringByRsa();
    log.info("加密后的账号为:{}",cipherStringByRsa.encrypt("loginName"));
    log.info("加密后的密码为:{}",cipherStringByRsa.encrypt("password"));
}
[INFO ][main] 加密后的账号为:zmUAo699M0BsYwko1R3htnaNDbRvg3W7Kri8vaWYZIXRMKv2j45/F1GPTK2sJXMhGZqz29LpzMiDLiuYAMZCoAG01uaD/zJTzJkIgYgxQ4G/Mgj3L2ezRBGPUXaAGjgk5l0fqwJShuUopa1YQqPlfMXx1mlMFF+YQGhQhABuKkw= (CipherTest.java:33)
[INFO ][main] 加密后的密码为:AJFpCyUZF2LXdPfZUExm8og1JYzzdVFgkU5gBqOPUTQ2CNxRJUW7j8cboxtaAUi9yyTUBPBszQM6QmWhUSAghPVHTMHd4eaEybzTZ/y0CfDo19nwcuYMRwSbKPovpZEA2YiVV/3IjFad3VGlvZiJNnY8uo2sMsDPs2Y8GBPVTE8= (CipherTest.java:34)

4.发送请求

请求体如下:

{
    "loginName": "zmUAo699M0BsYwko1R3htnaNDbRvg3W7Kri8vaWYZIXRMKv2j45/F1GPTK2sJXMhGZqz29LpzMiDLiuYAMZCoAG01uaD/zJTzJkIgYgxQ4G/Mgj3L2ezRBGPUXaAGjgk5l0fqwJShuUopa1YQqPlfMXx1mlMFF+YQGhQhABuKkw=",
    "password": "AJFpCyUZF2LXdPfZUExm8og1JYzzdVFgkU5gBqOPUTQ2CNxRJUW7j8cboxtaAUi9yyTUBPBszQM6QmWhUSAghPVHTMHd4eaEybzTZ/y0CfDo19nwcuYMRwSbKPovpZEA2YiVV/3IjFad3VGlvZiJNnY8uo2sMsDPs2Y8GBPVTE8="
}

服务端收到的数据体为:

[INFO ] 收到的数据体:LoginForm(loginName=loginName, password=password)

服务端利用密钥解密后得到的数据与根据公钥加密前的数据一致,案例通过。