当前位置:

首页 > 编程开发 > SpringBoot怎么通过自定义classloader加密保护class文件

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

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

本文目录

    背景

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

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

    maven插件加密

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

     @Parameter(name = "protectClassNames", defaultValue = "")
     private List protectClassNames;
     @Parameter(name = "noCompileClassNames", defaultValue = "")
     private List noCompileClassNames;
     private List 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);
            }
        }

    注意事项

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

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

    3.针对自定义插件的列表形式的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的内容进行隐藏

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

    2.将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);
        }

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

    pom文件增加动态编译依赖

            
                org.codehaus.groovy
                groovy-all
                2.4.13
            

    获取文件内容进行编译代码如下(写入/读取注意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中的方法,例如

    @Component
    public 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增加依赖

            
                com.esotericsoftware
                reflectasm
                1.11.0
            

    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;
        }
    }
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    springBoot项目常用目录有哪些
    springBoot项目常用目录有哪些

    springBoot项目常用目录springBoot项目的目录结构及名规范介绍基于SpringBoot开发时的目录结构及命名规范,通过介绍能够为你解决,在实际项目中如何规划目录结构?如何命名目录更规范?各个目录分别代表什么含义?等三个问题。目录说明servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1|-servicex-common//模块2|-servicex-gateway//模块3|-serv

    在Java中如何实现类的动态加载功能
    在Java中如何实现类的动态加载功能

    Java中的ClassLoader函数可以实现在运行时动态加载类,这在一些需要灵活地部署和修改代码的应用程序中非常有用。通过ClassLoader的功能,可以实现插件机制,提高系统的可扩展性和灵活性。本文将介绍如何使用ClassLoader函数进行类动态加载。一、ClassLoader的作用在启动Java虚拟机(JVM)时,会创建三个ClassLoader:

    深入解析PHP Class的应用,使代码更易理解
    深入解析PHP Class的应用,使代码更易理解

    在编写PHP代码时,使用类(Class)是一个非常常见的做法。通过使用类,我们可以将相关的功能和数据封装在一个单独的单元中,使代码更加清晰、易读和易维护。本文将详细介绍PHPClass的用法,并提供具体的代码示例,帮助读者更好地理解如何在实际项目中应用类来优化代码。1.创建和使用类在PHP中,可以使用关键字class来定义一个类,并在类中定义属性和方法。

    精通PHP Class:提高代码的可维护性
    精通PHP Class:提高代码的可维护性

    PHP是一种广泛用于开发Web应用程序的编程语言,其中的Class是面向对象编程中非常重要的概念。正确地使用PHPClass能够提高代码的可维护性,使代码更加清晰、结构化,并且易于扩展和修改。下面将通过具体的代码示例来介绍如何掌握PHPClass用法,以提升代码的可维护性。首先,让我们从一个简单的示例开始。假设我们需要创建一个表示动物的类,我们可以定义一

    掌握Python元编程,成就编码大师
    掌握Python元编程,成就编码大师

    文章什么是元编程?在学习元编程之前,我们需要先了解两个重要的概念:类和元类。类是一个蓝图,它可以用来创建对象。而元类则是类的蓝图,它可以用来创建类。你可以把元类理解成一个工厂,它可以生产出各种各样的类。元编程的优势:更加强大的控制力。通过元编程,我们可以控制类的创建过程,从而获得对类的更加强大的控制力。更加灵活的语言结构。通过元编程,我们可以创建出更加灵活的语言结构,从而使python更加强大。更加健壮的代码。通过元编程,我们可以编写出更加健壮的代码,从而提高代码的质量。如何学习元编程?学习元编程,首先需

    解析SpringBoot和SpringMVC之间的异同
    解析SpringBoot和SpringMVC之间的异同

    解析SpringBoot和SpringMVC之间的异同SpringBoot和SpringMVC是Java领域中非常重要的开发框架。虽然它们都属于Spring框架的一部分,但是在使用和功能上有一些明显的区别。本文将对SpringBoot和SpringMVC进行比较,解析它们之间的异同。首先,让我们来了解一下SpringBoot。SpringBo

    探索SpringCloud和SpringBoot在微服务领域的不同应用方式
    探索SpringCloud和SpringBoot在微服务领域的不同应用方式

    近年来,随着云计算和分布式架构的兴起,微服务架构的应用越来越广泛。而SpringCloud和SpringBoot作为Java开发中的两个重要框架,对于微服务的实现起到了重要的作用。然而,很多人对于它们在微服务领域的不同应用方式还存在一定的疑惑。本文将从不同的角度探索SpringCloud和SpringBoot在微服务中的应用方式。首先,我们来了解一下Spri

    SpringBoot和SpringMVC有何区别?
    SpringBoot和SpringMVC有何区别?

    SpringBoot与SpringMVC的不同之处在哪里?SpringBoot和SpringMVC是两个非常流行的Java开发框架,用于构建Web应用程序。尽管它们经常分别被使用,但它们之间的不同之处也是很明显的。首先,SpringBoot可以被看作是一个Spring框架的扩展或者增强版。它旨在简化Spring应用程序的初始化和配置过程,以帮助开发人

    比较SpringBoot和SpringMVC的异同
    比较SpringBoot和SpringMVC的异同

    SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

    SpringBoot与SpringMVC的不同点是什么?
    SpringBoot与SpringMVC的不同点是什么?

    SpringBoot和SpringMVC是Java开发中常用的两个框架,它们都是由Spring框架所提供的,但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。一、SpringBoot的特点:简化配置:SpringBoot通过约定优于配置的原则,大大简化了项目的配置过程。它可以自动配置项目所需要的参数,开发人

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。