返回顶部
首页 > 资讯 > 精选 >SpringBoot怎么通过自定义classloader加密保护class文件
  • 594
分享到

SpringBoot怎么通过自定义classloader加密保护class文件

2023-06-30 09:06:02 594人浏览 独家记忆
摘要

今天小编给大家分享一下SpringBoot怎么通过自定义classloader加密保护class文件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面

今天小编给大家分享一下SpringBoot怎么通过自定义classloader加密保护class文件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

背景

最近针对公司框架进行关键业务代码进行加密处理,防止通过jd-gui等反编译工具能够轻松还原工程代码,相关混淆方案配置使用比较复杂且针对springboot项目问题较多,所以针对class文件加密再通过自定义的classloder进行解密加载,此方案并不是绝对安全,只是加大反编译的困难程度,防君子不防小人,整体加密保护流程图如下图所示

SpringBoot怎么通过自定义classloader加密保护class文件

Maven插件加密

使用自定义maven插件对编译后指定的class文件进行加密,加密后的class文件拷贝到指定路径,这里是保存到resource/coreclass下,删除源class文件,加密使用的是简单的DES对称加密

 @Parameter(name = "protectClassNames", defaultValue = "") private List<String> protectClassNames; @Parameter(name = "noCompileClassNames", defaultValue = "") private List<String> noCompileClassNames; private List<String> protectClassNameList = new ArrayList<>(); private void protectCore(File root) throws ioException {        if (root.isDirectory()) {            for (File file : root.listFiles()) {                protectCore(file);            }        }        String className = root.getName().replace(".class", "");        if (root.getName().endsWith(".class")) {            //class筛选            boolean flag = false;            if (protectClassNames!=null && protectClassNames.size()>0) {                for (String item : protectClassNames) {                    if (className.equals(item)) {                        flag = true;                    }                }            }            if(noCompileClassNames.contains(className)){                boolean deleteResult = root.delete();                if(!deleteResult){                    System.GC();                    deleteResult = root.delete();                }                System.out.println("【noCompile-deleteResult】:" + deleteResult);            }            if (flag && !protectClassNameList.contains(className)) {                protectClassNameList.add(className);                System.out.println("【protectCore】:" + className);                FileOutputStream fos = null;                try {                    final byte[] instrumentBytes = doProtectCore(root);                    //加密后的class文件保存路径                    String folderPath = output.getAbsolutePath() + "\\" + "classes";                    File  folder = new File(folderPath);                    if(!folder.exists()){                        folder.mkdir();                    }                    folderPath = output.getAbsolutePath() + "\\" + "classes"+ "\\" + "coreclass" ;                    folder = new File(folderPath);                    if(!folder.exists()){                        folder.mkdir();                    }                    String filePath = output.getAbsolutePath() + "\\" + "classes" + "\\" + "coreclass" + "\\" + className + ".class";                    System.out.println("【filePath】:" + filePath);                    File protectFile = new File(filePath);                    if (protectFile.exists()) {                        protectFile.delete();                    }                    protectFile.createNewFile();                    fos = new FileOutputStream(protectFile);                    fos.write(instrumentBytes);                    fos.flush();                } catch (MojoExecutionException e) {                    System.out.println("【protectCore-exception】:" + className);                    e.printStackTrace();                } finally {                    if (fos != null) {                        fos.close();                    }                    if(root.exists()){                        boolean deleteResult = root.delete();                        if(!deleteResult){                            System.gc();                            deleteResult = root.delete();                        }                        System.out.println("【protectCore-deleteResult】:" + deleteResult);                    }                }            }        }    }    private byte[] doProtectCore(File clsFile) throws MojoExecutionException {        try {            FileInputStream inputStream = new FileInputStream(clsFile);            byte[] content = ProtectUtil.encrypt(inputStream);            inputStream.close();            return content;        } catch (Exception e) {            throw new MojoExecutionException("doProtectCore error", e);        }    }

注意事项

加密后的文件也是class文件,为了防止在递归查找中重复加密,需要对已经加密后的class名称记录防止重复

在删除源文件时可能出现编译占用的情况,执行System.gc()后方可删除

针对自定义插件的列表形式的configuration节点可以使用List来映射

插件使用配置如图所示

SpringBoot怎么通过自定义classloader加密保护class文件

自定义classloader

创建CustomClassLoader继承自ClassLoader,重写findClass方法只处理装载加密后的class文件,其他class交有默认加载器处理,需要注意的是默认处理不能调用super.finclass方法,在idea调试没问题,打成jar包运行就会报加密的class中的依赖class无法加载(ClassnodefException/ClassNotFoundException),这里使用的是当前线程的上下文的类加载器就没有问题(Thread.currentThread().getContextClassLoader())

public class CustomClassLoader extends ClassLoader {    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        Class<?> clz = findLoadedClass(name);        //先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。        if (clz != null) {            return clz;        }        String[] classNameList = name.split("\\.");        String classFileName = classNameList[classNameList.length - 1];        if (classFileName.endsWith("MethodAccess") || !classFileName.endsWith("CoreUtil")) {            return Thread.currentThread().getContextClassLoader().loadClass(name);        }        ClassLoader parent = this.getParent();        try {            //委派给父类加载            clz = parent.loadClass(name);        } catch (Exception e) {            //log.warn("parent load class fail:"+ e.getMessage(),e);        }        if (clz != null) {            return clz;        } else {            byte[] classData = null;            ClassPathResource classPathResource = new ClassPathResource("coreclass/" + classFileName + ".class");            InputStream is = null;            try {                is = classPathResource.getInputStream();                classData = DESEncryptUtil.decryptFromByteV2(FileUtil.convertStreamToByte(is), "xxxxxxx");            } catch (Exception e) {                e.printStackTrace();                throw new ProtectClassLoadException("getClassData error");            } finally {                try {                    if (is != null) {                        is.close();                    }                } catch (IOException e) {                    e.printStackTrace();                }            }            if (classData == null) {                throw new ClassNotFoundException();            } else {                clz = defineClass(name, classData, 0, classData.length);            }            return clz;        }    }}

隐藏classloader

classloader加密class文件处理方案的漏洞在于自定义类加载器是完全暴露的,只需进行分析解密流程就能获取到原始class文件,所以我们需要对classloder的内容进行隐藏

把classloader的源文件在编译期间进行删除(maven自定义插件实现)

将classloder的内容进行base64编码后拆分内容寻找多个系统启动注入点写入到loader.key文件中(拆分时写入的路径和文件名需要进行base64加密避免全局搜索),例如

    private static void init() {        String source = "dCA9IG5hbWUuc3BsaXQoIlxcLiIpOwogICAgICAgIFN0cmluZyBjbGFzc0ZpbGVOYW1lID0gY2xhc3NOYW1lTGlzdFtjbGFzc05hbWVMaXN0Lmxlbmd0aCAtIDFdOwogICAgICAgIGlmIChjbGFzc0ZpbGVOYW1lLmVuZHNXaXRoKCJNZXRob2RBY2Nlc3MiKSB8fCAhY2xhc3NGaWxlTmFtZS5lbmRzV2l0aCgiQ29yZVV0aWwiKSkgewogICAgICAgICAgICByZXR1cm4gVGhyZWFkLmN1cnJlbnRUaHJlYWQoKS5nZXRDb250ZXh0Q2xhc3NMb2FkZXIoKS5sb2FkQ2xhc3MobmFtZSk7CiAgICAgICAgfQogICAgICAgIENsYXNzTG9hZGVyIHBhcmVudCA9IHRoaXMuZ2V0UGFyZW50KCk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgLy/lp5TmtL7nu5nniLbnsbvliqDovb0KICAgICAgICAgICAgY2x6ID0gcGFyZW50LmxvYWRDbGFzcyhuYW1lKTsKICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAvL2xvZy53YXJuKCJwYXJlbnQgbG9hZCBjbGFzcyBmYWls77yaIisgZS5nZXRNZXNzYWdlKCksZSk7CiAgICAgICAgfQogICAgICAgIGlmIChjbHogit0gbnVsbCkgewogICAgICAgICAgICByZXR1cm4gY2x6OwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGJ5dGVbXSBjbGFzc0RhdGEgPSBudWxsOwogICAgICAgICAgICBDbGFzc1BhdGhSZXNvdXJjZSBjbGFzc1BhdGhSZXNvdXJjZSA9IG5ldyBDbGFzc1BhdGhSZXNvdXJjZSgiY29yZWNsYXNzLyIgKyBjbGFzc0ZpbGVOYW1lICsgIi5jbGFzcyIpOwogICAgICAgICAgICBJbnB1dFN0cmVhbSBpcyA9IG51bGw7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBpcyA9IGNsYXNzUGF0aFJlc291cmNlLmdldElucHV0U3RyZWFtKCk7CiAgICAgICAgICAgICAgICBjbGFzc0RhdGEgPSBERVNFbmNyeXB0VXRpbC5kZWNyeXB0RnJvbUJ5dGVWMihGaWxlVXRpbC5jb252ZXJ0U3RyZWFtVG9CeXRlKGlzKSwgIlNGQkRiRzkxWkZoaFltTmtNVEl6TKE9PSIpOwogICAgICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UoKTsKICAgICAgICAgICAgICAgIHRocm93IG5ldyBQc";        String filePath = "";        try{            filePath = new String(Base64.decodeBase64("dGVtcGZpbGVzL2R5bmFtaWNnZW5zZXJhdGUvbG9hZGVyLmtleQ=="),"utf-8");        }catch (Exception e){            e.printStackTrace();        }        FileUtil.writeFile(filePath, source,true);    }

通过GroovyClassLoader对classloder的内容(字符串)进行动态编译获取到对象,删除loader.key文件

pom文件增加动态编译依赖

        <dependency>            <groupId>org.codehaus.groovy</groupId>            <artifactId>groovy-all</artifactId>            <version>2.4.13</version>        </dependency>

获取文件内容进行编译代码如下(写入/读取注意utf-8处理防止乱码)

public class CustomCompile {    private static Object Compile(String source){        Object instance = null;        try{            // 编译器            CompilerConfiguration config = new CompilerConfiguration();            config.setSourceEncoding("UTF-8");            // 设置该GroovyClassLoader的父ClassLoader为当前线程的加载器(默认)            GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);            Class<?> clazz = groovyClassLoader.parseClass(source);            // 创建实例            instance = clazz.newInstance();        }catch (Exception e){            e.printStackTrace();        }        return instance;    }    public static  ClassLoader getClassLoader(){        String filePath = "tempfiles/dynamicgenserate/loader.key";        String source = FileUtil.readFileContent(filePath);        byte[] decodeByte = Base64.decodeBase64(source);        String str = "";        try{            str = new String(decodeByte, "utf-8");        }catch (Exception e){            e.printStackTrace();        }finally {            FileUtil.deleteDirectory("tempfiles/dynamicgenserate/");        }        return (ClassLoader)Compile(str);    }}

被保护class手动加壳

因为相关需要加密的class文件都是通过customerclassloder加载的,获取不到显示的class类型,所以我们实际的业务类只能通过反射的方法进行调用,例如业务工具类LicenseUtil,加密后类为LicenseCoreUtil,我们在LicenseUtil的方法中需要反射调用,LicenseCoreUtil中的方法,例如

@Componentpublic class LicenseUtil {    private String coreClassName = "com.haopan.frame.core.util.LicenseCoreUtil";    public String getMachineCode() throws Exception {        return (String) CoreLoader.getInstance().executeMethod(coreClassName, "getMachineCode");    }    public boolean checkLicense(boolean startCheck) {        return (boolean)CoreLoader.getInstance().executeMethod(coreClassName, "checkLicense",startCheck);    }}

为了避免反射调用随着调用次数的增加损失较多的性能,使用了一个第三方的插件reflectasm,pom增加依赖

        <dependency>            <groupId>com.esotericsoftware</groupId>            <artifactId>reflectasm</artifactId>            <version>1.11.0</version>        </dependency>

reflectasm使用了MethodAccess快速定位方法并在字节码层面进行调用,CoreLoader的代码如下

public class CoreLoader {    private ClassLoader classLoader;    private CoreLoader() {        classLoader = CustomCompile.getClassLoader();    }    private static class SingleInstace {        private static final CoreLoader instance = new CoreLoader();    }    public static CoreLoader getInstance() {        return SingleInstace.instance;    }    public Object executeMethod(String className,String methodName, Object... args) {        Object result = null;        try {            Class clz = classLoader.loadClass(className);            MethodAccess access = MethodAccess.get(clz);            result = access.invoke(clz.newInstance(), methodName, args);        } catch (Exception e) {            e.printStackTrace();            throw  new ProtectClassLoadException("executeMethod error");        }        return result;    }}

以上就是“SpringBoot怎么通过自定义classloader加密保护class文件”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网精选频道。

--结束END--

本文标题: SpringBoot怎么通过自定义classloader加密保护class文件

本文链接: https://lsjlt.com/news/328078.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • SpringBoot怎么通过自定义classloader加密保护class文件
    今天小编给大家分享一下SpringBoot怎么通过自定义classloader加密保护class文件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面...
    99+
    2023-06-30
  • SpringBoot详细讲解通过自定义classloader加密保护class文件
    目录背景maven插件加密注意事项自定义classloader隐藏classloader被保护class手动加壳总结背景 最近针对公司框架进行关键业务代码进行加密处理,防止通过jd-...
    99+
    2024-04-02
  • springboot怎么通过@PropertySource加载自定义yml文件
    小编给大家分享一下springboot怎么通过@PropertySource加载自定义yml文件,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!@PropertyS...
    99+
    2023-06-29
  • springboot如何通过@PropertySource加载自定义yml文件
    目录@PropertySource加载自定义yml文件@PropertySource注解对于yml的支持@PropertySource加载自定义yml文件 使用@PropertySo...
    99+
    2024-04-02
  • java怎么通过注解加载自定义配置文件
    在Java中,可以使用注解来加载自定义配置文件。具体步骤如下:1. 创建一个自定义的注解,用来标记需要加载的配置文件。```java...
    99+
    2023-09-17
    java
  • Android中怎么通过自定义ImageView添加文字说明
    本篇文章为大家展示了Android中怎么通过自定义ImageView添加文字说明,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。MyImageTextViewNew.javapublic c...
    99+
    2023-05-30
    android imageview
  • Windows10怎么自定义设置3D文字屏幕保护?
    Windows10-10130版系统,个性化程序里没有屏幕保护的项目,如果我们需要屏幕保护而且想在屏幕保护上自制文字,达到独特的效果,应该如何设置呢?下面介绍Windows10-10130版系统打开屏幕保护以及在屏幕保护...
    99+
    2023-06-16
    Windows 10 屏幕 D 自定义 文字 Windows10
  • 怎么通过php使用gpg加密文件
    本篇内容介绍了“怎么通过php使用gpg加密文件”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!加密一个简单但又实用的任务就是发送加密电子邮件...
    99+
    2023-06-20
  • SpringBoot怎么通过自定义注解与异步来管理日志
    这篇文章主要介绍“SpringBoot怎么通过自定义注解与异步来管理日志”,在日常操作中,相信很多人在SpringBoot怎么通过自定义注解与异步来管理日志问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spr...
    99+
    2023-07-05
  • Android中怎么通过自定义RecyclerView控件实现Gallery效果
    这期内容当中小编将会给大家带来有关Android中怎么通过自定义RecyclerView控件实现Gallery效果,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1、RecyclerView的基本用法首先主...
    99+
    2023-05-30
    android recyclerview gallery
  • vue2怎么自定义组件通过rollup配置发布到npm
    本篇内容介绍了“vue2怎么自定义组件通过rollup配置发布到npm”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!创建Vue组件库项目首先...
    99+
    2023-07-05
  • Android中怎么通过自定义view实现进度条加载效果
    Android中怎么通过自定义view实现进度条加载效果,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。分析图:代码如下:package com.example.d...
    99+
    2023-05-30
    android view
  • 怎么在SpringBoot中通过自定义注解实现一个Token校验功能
    本篇文章为大家展示了怎么在SpringBoot中通过自定义注解实现一个Token校验功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。定义Token的注解,需要Token校验的接口,方法上加上此注解...
    99+
    2023-06-14
  • vue子组件中怎么通过自定义事件向父组件传递数据
    这篇文章将为大家详细讲解有关vue子组件中怎么通过自定义事件向父组件传递数据,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。使用v-on绑定自定义事件可以让子...
    99+
    2024-04-02
  • Android中怎么通过自定义控件实现下拉刷新效果
    本篇文章给大家分享的是有关Android中怎么通过自定义控件实现下拉刷新效果,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。@Override  protec...
    99+
    2023-05-30
    android
  • 怎么解决springboot读取自定义配置文件时出现乱码问题
    这篇文章主要介绍“怎么解决springboot读取自定义配置文件时出现乱码问题”,在日常操作中,相信很多人在怎么解决springboot读取自定义配置文件时出现乱码问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作