Java加密解密class文件,使用classLoader动态解密class文件

9 篇文章 0 订阅

该文章所涉及的代码,可以在此处下载:https://github.com/chengqianbygithub/JavaLearningDemos/tree/develop
在develop分支哦。

在日常开发中,可能会遇到要对系统中比较敏感的代码进行保护,下面就总结一下保护源码的方法中最简单的方式,即文件加密

加密和解密的大致思想
加密无非就是对class文件进行异或一下,解密呢,那就是再对class文件异或回来即可。

加密后的文件如果想要用到的话,就需要classLoader动态加载进来
具体实现详情:Java实现自定义classLoader类加载器动态解密class文件

1、加解密的实现方式

下面为加解密的实现方式,大部分的解释都放在代码注释里面了。

/**
 * 加解密类
 */
public class EdCipher {

    // 装加密后的Class文件的目录名
    private String encryptFolderName = "encrypt";

    /**
     * 加密方法
     * @param name 需要加密的文件名
     */
    public void encryptClass(String name) {
        // 获取加密前文件的绝对路径
        String path = getAbsolutePathBeforeEncryption(name);

        // 为待加密的class文件创建File对象
        File classFile = new File(path);
        if (!classFile.exists()) {
            // TODO 如果文件不存在,做相应的处理。一般情况下都是抛出异常;
        } else {
            // 在 待加密的文件也就是classFile的同级目录下创建一个文件夹,然后把加密后的文件放进去
            File encryptFolder = new File(classFile.getParent() + File.separator + encryptFolderName);
            if (!encryptFolder.exists()) {
                encryptFolder.mkdirs();
            }
        }
        // 加密后的文件需要单独放,就放在encryptFolderName文件夹下,encryptedFile 为加密后文件的全路径文件名
        String encryptedFile = classFile.getParent() + File.separator + encryptFolderName + File.separator + classFile.getName();
        try (
                FileInputStream fileInputStream = new FileInputStream(classFile);
                BufferedInputStream bis = new BufferedInputStream(fileInputStream);
                FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
                BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)
            ) {

            // 加密
            int data;
            while ((data = bis.read()) != -1) {
                bos.write(data ^ 0xFF);
            }
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 现在将原来未加密的文件删除
        classFile.delete();

        // 先获取加密前文件绝对路径名,然后修改后缀,再创建File对象
        File oldFile = new File(path + "en");
        // 删除未加密前的文件
        if (oldFile.exists()) {
            oldFile.delete();
        }
        // 根据加密后的文件创建File对象
        File newEncryptedFile = new File(encryptedFile);
        // 将加密后的对象重命名,这时加密后的文件就把加密前的文件替换掉了,这就是为什么刚开始加密后的文件需要单独放的原因
        newEncryptedFile.renameTo(oldFile);
        // 删除之前的加密文件夹下面的加密文件
        newEncryptedFile.getParentFile().delete();
    }

    /**
     * 解密方法
     * @param name 需要解密的文件名
     */
    protected byte[] decryptClass(String name) {
        String path;
        if (!name.contains(".class")) {
            path = getAbsolutePathAfterEncryption(name);
        } else {
            path = name;
        }
        // 为待加密的文件创建File对象
        File decryptClassFile = new File(path);
        if (!decryptClassFile.exists()) {
            System.out.println("decryptClass() File:" + path + " not found!");
            return null;
        }
        byte[] result = null;
        BufferedInputStream bis = null;
        ByteArrayOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(decryptClassFile));
            bos = new ByteArrayOutputStream();
            // 解密
            int data;
            while ((data = bis.read()) != -1) {
                bos.write(data ^ 0xFF);
            }
            bos.flush();
            result = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 获取加密前文件的绝对路径
     * @param name 待加密的文件的全限定名
     * @return 文件的绝对路径
     * 注意:根据待加密的文件的位置不同(外部class,项目内部class),改写改方法。
     * 最终目的就是获取到待加密的文件的绝对路径
     */
    private String getAbsolutePathBeforeEncryption(String name) {
        /*String className = name.substring(name.lastIndexOf(".") + 1) + ".class";
        String path = EdCipher.class.getResource(className).toString();
        path = path.substring(path.indexOf("file:/") + "file:/".length());
        if (System.getProperty("os.name").toUpperCase().contains("LINUX")) {
            path = File.separator + path;
        }*/
        return new File(name).getAbsolutePath();
    }

    /**
     * 获取加密后文件的绝对路径
     * @param name 待解密的文件的全限定名
     * @return 文件的绝对路径
     * 注意:根据加密后文件的位置不同(外部class,项目内部class),改写改方法。
     * 最终目的就是获取到加密后文件的绝对路径
     */
    private String getAbsolutePathAfterEncryption(String name) {
        /*String className = name.substring(name.lastIndexOf(".") + 1) + ".classen";
        String path = EdCipher.class.getResource(className).toString();
        path = path.substring(path.indexOf("file:/") + "file:/".length());*/
        return new File(name).getAbsolutePath();
    }

    // 测试
    public static void main(String[] args) {
        EdCipher edCipher = new EdCipher();
        edCipher.encryptClass("F:\\Test.class");
         
        // 如果使用Ant打包就可以将上面这句huanchen换成下面这句话。
        // edCipher.encryptClass(args[0]);
    }
}

1.1、小技巧。

在加密方法的最后为什么要修改文件的后缀呢?
这里在文件后面加了一个“en”,最后生成的文件就是xxx.classen,这样做的目的是为了在JVM的Application ClassLoader类加载器加载classpath下面的class字节码时,区别于普通的class字节码文件,因为该文件已经被加密了。如果类加载器将它加载进了虚拟机,那么在进行类加载过程中的验证阶段中的文件格式验证环节的时候,就会验证出不符合Class文件格式的约束,虚拟机就会抛出一个java.lang.VerifyError异常或其子类异常,假设使用的HTTP服务器是tomcat,那么tomcat就会报错,启动不起来。这算是一个小技巧。

2、拓展:Ant打包时加密

如果要想在Ant打包的时候,就加密文件,就需要在build.xml配置文件中调用该类的Main方法即可;

<!-- 加密 -->
<target name="encrypt">
	<java classname="EdCipher" failonerror="true">
		<classpath refid="classpath.run"/>
		<arg line="需要加密的文件的包名+文件名"/>
	</java>
</target>

技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值