JFinal使用技巧-AES加密解密数据库字段

如题,我们客户有要求 敏感字段 入库要加密处理。以及有权限的人登录后还可查看原文。看到反馈区有社友使用数据库的函数加密,感觉不妥。1 是不方便 操作,2 是数据库的函数处理一般都比较耗费 cpu。所以放到 Java 这边来处理感觉是更好的。
废话不多说,上 石马 !
工具类:

/**
 * 对称加密工具
 * @author dufuzhong 
 */
@SuppressWarnings("unused")
public class AesStrKit {
    private static String aesKey;
    private static String getAesKey() {
        if (aesKey == null){
            //配置文件获取秘钥 PropKit工具
            aesKey = AppConfig.get("aesKey");
            Objects.requireNonNull(aesKey, "配置文件 aesKey 未配置秘钥");
        }
        return aesKey;
    }

    public static void main(String[] args) {
        /* 放开注释 执行 main方法 可以获得一个秘钥
        aesKey = AesKit.genAesKey();
        LogKit.info("获取一个秘钥,注意需要自己复制下面内容,写入到配置文件中才可以更新:");
        LogKit.info("aesKey=" + aesKey);
         */

        //修改 表名 与 字段名 后 执行  main方法 即可
        //启动数据库连接池与Db配置插件
//        AppConfig.plugins(() -> {
//            dbEncode("表名1", "phone", "idCard", "email", "bankCard");
//            dbEncode("表名2", "xxx");
//        });
    }

    /**
     * 数据库旧数据 明文 转换为 密文
     * @param table 需要处理的表
     * @param columns 需要处理的字段
     */
    public static void dbEncode(String table, String... columns) {
        Kv kv = Kv.by("table", table).set("columns", columns);
        //数据库字段长度修改为 varchar255, 一般业务够用, 看业务情况自己加
        String sqlModify = "ALTER TABLE #(table) " +
                "#for(x : columns) #if(!for.first),#end MODIFY COLUMN `#(x)` varchar(255) #end ";
        Db.templateByString(sqlModify, kv).update();
        LogKit.info("数据库字段长度修改为255");

        String sqlFind = "select id #for(x : columns), #(x) #end from #(table)";
        Db.templateByString(sqlFind, kv).each(record -> {
            for (String column : columns) {
                String v = record.getStr(column);
                if (StrKit.notBlank(v) && v.length() < 30){
                    LogKit.debug(v);
                    record.set(column, encode(v));
                }
            }
            Db.update(table, record);
            return true;
        });
        LogKit.info(table + "》dbEncode执行完成");
    }

    /**
     * 加密
     * @param content 明文
     * @return 密文
     */
    public static String encode(String content) {
        if (StrKit.isBlank(content)){
            return content;
        }
        return Base64Kit.encode(AesKit.encrypt(content, getAesKey()));
    }

    /**
     * 解密
     * @param encode 密文
     * @return 明文
     */
    public static String decrypt(String encode) {
        if (StrKit.isBlank(encode)){
            return encode;
        }
        try {
            return AesKit.decryptToStr(Base64Kit.decode(encode), getAesKey());
        }catch (RuntimeException ignored){
            //LogKit.error("解密异常:" + encode);
            //解密异常 目前不让报错处理,页面正常显示其他内容 
            return encode;
        }
    }

}

工具相对简单。AesKey 是放在配置文件里面的。

在需要入库加密的字段前 record.set(column, AesStrKit.encode(v)); 调用一下即可,

上面有一个数据库旧数据 明文 转换为 密文的处理工具就是这样。 

密文展示为原文也是一样,调用一下解密即可。


如果没有处理历史数据的场景,可以把 dbEncode 方法移除即可,下面代码也不用参考了。

其中有用到一个 AppConfig 类,是JFinalConfig子类,我这边部分代码贴一下(大家自己项目的都不一样):

public class AppConfig extends JFinalConfig {
    private static Prop p = null;

    public static Prop p() {
        if (p == null){
            p = PropKit.append("apiConfig.txt").appendIfExists("apiConfig_pro.txt");
            //设置开发模式
            JFinal.me().getConstants().setDevMode(p.getBoolean("devMode"));
        }
        return p;
    }
    
    public static String get(String key){
        return p().get(key);
    }
    
    //...省略 JF 配置代码
    
    /**
     * 独立启动插件, 如启动数据库等, 使用完后自动关闭
     */
    public static void plugins(Runnable runnable){
        PluginsPlugin p = new PluginsPlugin();
        new AppConfig().configPlugin(p.getPlugins());
        p.run(runnable);
    }
}

经常用到需要独立启动的测试类,所以对Plugins打包了一下:

import com.jfinal.config.Plugins;
import com.jfinal.plugin.IPlugin;

import java.util.List;

public class PluginsPlugin implements IPlugin {
    private Plugins ps = new Plugins();

    public static PluginsPlugin of(IPlugin... plugins){
        PluginsPlugin pp = new PluginsPlugin();
        if (plugins != null) {
            for (IPlugin plugin : plugins) {
                pp.add(plugin);
            }
        }
        return pp;
    }

    public Plugins getPlugins() {
        return ps;
    }

    public PluginsPlugin add(IPlugin plugin) {
        ps.add(plugin);
        return this;
    }

    public void run(Runnable runnable){
        this.start();
        try{
            runnable.run();
        }finally {
            this.stop();
        }
    }

    @Override
    public boolean start() {
        for (IPlugin p : ps.getPluginList()) {
            p.start();
        }
        return true;
    }

    @Override
    public boolean stop() {
        List<IPlugin> list = ps.getPluginList();
        for (int i = list.size() - 1; i >= 0; i--) {
            list.get(i).stop();
        }
        return true;
    }
}

好了分享结束~ 

提前祝愿大家新的一年里!工作顺利~ 家庭温馨~ 事业红火~

评论区

zzutligang

2024-01-27 18:05

赞一个

JFinal

2024-01-27 18:18

小杜总有能好东西分享,点赞收藏一波

zeroabc

2024-01-30 11:56

收藏

北流家园网

2024-03-10 09:07

收藏,杜总分享的都是极品

hb963724769

2024-04-01 10:44

是我,我感觉也是数据库不妥,但是是最快的上线方式了。应对亚马逊审查。后来数据库无法解析函数,最后也是在Java后台加密的。大概是这样。
public M setAddr(String addr) {
String systemKey = SystemKeyPermission.getSystemKey();
set("addr", Aes32Kit.encode(addr,systemKey));
return (M)this;
}

hb963724769

2024-04-01 10:54

查询解密怎么弄比较高效呢。比如原来有个分页查询的界面,是查出来结果之后循环遍历解密呢。还是直接数据库解密呢。感觉应该是要循环解密吧,也用后台去解密。但是目前我为了快速上,我直接数据库函数解密。
CONVERT(AES_decrypt(FROM_BASE64(wsr.addr),#para(systemKey),#para(systemKeyIv)) USING utf8) AS addr

hb963724769

2024-04-01 10:55

@hb963724769 循环解密还有一个问题是,系统N处用了这个地址信息。每个地方代码都要改过去,又麻烦风险又高。哪里漏改了就凉了。

杜福忠

2024-04-02 10:48

@hb963724769 查询需要看业务场景了,比如手机号 是全匹配查询,还是号段匹配查询,全匹配就加密后用加密串去查,分段查询就数据库提前冗余字段存储分段的值。比如地址查询,也是一样操作。还是看业务场景选择合适的查询。数据库解密函数查询相对比较耗费 cpu,主要也看服务器配置和数据量,返回时间能满足业务要求也是没问题的,还简单些

杜福忠

2024-04-02 10:54

@hb963724769 主要是入库使用Java 加密后再入库,查询这个就是看情况了。 我们系统查询位置有统一查询方法,走同一个方法,并且需要加密的字段名是有标识,所以Java会自动根据规则是否解密还是打码处理展示的

热门分享

扫码入社