基于mybatis数据自动加密解密之深度加解密

135 阅读3分钟

之前我写了一篇文章讲述了怎么利用mybatis拦截器自动在入库的时候加密出库的时候解密,但是有一天另一个开发跟我说这个对象里面还是一个对象它的字段加了注解怎么没用,我说当然没用了,因为我只处理第一层根本没考虑深度处理。然后想了想不就是加个递归处理的事吗,而且也好像挺合理的。

然后我就开始了改一段代码留下一个BUG,改一个BUG产生另一个BUG的旅途。

首先迎来的第一个问题就是循环引用会导致无限递归的问题,这个问题我直接限制了递归深度为3层,当时想着3层应该够用了,但是后面发现递归下去需要处理的字段是指数增长的,3层的时候加上我系统还有个bug就明显能感觉到速度慢了,最后改为了2层,而且正常情况下2层也够了。

等到解决了深度处理的问题后,又发现出现了重复加密,也就是一个字段加密了多次,存到数据库的值越来越长,跟着代码调试也没发现相互引用或者交叉引用的情况啊怎么就被重复加密了?最后发现在是有个地方用了缓存,在查库和写库的时候都是使用的同一个对象,而查库和写库都需要对相关字段进行加密,就导致了字段被重复加密,这里解决的思路有几种: 一是直接改缓存的业务代码,取缓存的时候返回一个拷贝的副本,当然这个方式也不太好不够通用,当时时间比较急作为了一种应急的方式。二是查库的入参拷贝一个副本然后再加密。三是找出加密后的形式规律,加密时跳过已加密数据。 后面我是选择了第三种方式,并且移除了我第一版代码中对查库的入参拷贝加密的方式,因为我加密后的数据用的是16进制编码,而且加密后的长度最短是32个16进制数,所以满足长度是32的倍数并且是16进制数的就认为是已加密的数据。而对象拷贝呢由于我们基础平台的问题,get方法存在相互引用会死循环。

下面我们来看代码,这里只贴出关键部分代码

// 判断是否是已加密数据
// 判断是否是16进制数
Pattern pattern = Pattern.compile("[\da-f]+");
// 加密后的16进制数必然a-f之间的值
final Pattern pattern2 = Pattern.compile("[a-f]+");
// 由于需要加密的数据都是身份证,手机号这种,加密后不应该超过64位,长度超过64的直接返回避免多次加密
// 加密后的数据为16进制数据,加密后最小32位16进制数,原文长度小于16时密文32,长度小于32时密文64
Matcher matcher = pattern.matcher(value.toLowerCase());
Matcher matcher2 = pattern2.matcher(value.toLowerCase());
// 长度为32的倍数且是16进制数字符串时认为是已加密的数据
boolean encrypted = value.length() % 32 == 0 && matcher.matches() && matcher2.find();
if (encrypted) {
    return value;
}

加密方法增加了递归调用的逻辑

// 对外还是保持一样的入参条件
public static void encrypt(Object paramsObject) throws IllegalAccessException {
    encrypt(paramsObject, 0);
}
/**
 * 递归方法
 * @param deep 当前的递归层数
 */
public static void encrypt(Object paramsObject, int deep) throws IllegalAccessException {
    deep++;
    if (paramsObject != null && needToDecrypt(paramsObject)) {
        Class<?> paramsClass = paramsObject.getClass();
        //取出当前当前类所有字段,传入加密方法
        Field[] declaredFields = paramsClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                //暂时只实现String类型的加密
                if (object instanceof String) {
                    String value = (String) object;
                    //加密  加密工具
                    field.set(paramsObject, encryptValue(value));
                }
            } else if (deep <= recursion_deep) {
                // 限制递归深度
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                encrypt(object, deep);
            }
        }
    }
}

同理解密的方法也是按一样的逻辑增加递归调用