这里写自定义目录标题)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文主要介绍利用JVMTI加密JAVA源码的实践方法以及SpringBoot项目加密的解决方案,并对几种常见的加密方式进行对比分析,JVMTI加密网上已经有很多相似文章,但对于SpringBoot包加密的方案及过程中遇到的问题都没有讲的太清晰,针对这个问题,文中着重用一章节内容来分析讨论,相信大家认真读完后会对SpringBoot包的加密问题及其框架运行方式有进一步深入的理解。
1. 为什么要对源码加密
技术方面:首先强调下这里的源码加密指的是对使用Java开发的软件产品源码进行加密,Java是解释型语言(实事上是编译型与解释型混合),在生成软件产品时源代码被编译成有规范可循的二进制字节码.class文件进行打包,然后由JVM将.class解释给CPU执行,而非C/C++、C#等编译型语言一样源码被直接编译成CPU可执行的机器码,所以我们拿到Java的软件产品后,对照相应的编译规范,很容易将.class文件内容还原成源代码,即便是人工还原也只是时间问题,不存在很困难的技术问题,也就是说.class文件基本上可以等同于源码文件,而这里说的源码加密也主要是对.class文件进行处理,使其在现有的手段下难以被反编译还原。
生产方面:源码加密最主要的原因就是为了保护知识产权,不论是个人还是公司、单位,软件都是劳动的成果,而源码就是成果的核心,加密就是为了保护劳动成果不被窃取。
安全方面:软件交付部署后,尤其是web应用,如果被还原成源码,很容易理清内部业务运行的逻辑关系或发现潜在漏洞,被非法利用操作,对系统数据产生破坏,因此安全也是源码加密的重要因素。
2. 源码加密的方法
加密的目的是人读不懂机器明白,所以方法也要从这两点入手,对于解释型语言,一般有混淆和字节码转换两大类:
(1)混淆
主要思路就是通过对变量、函数、类名等进行替换,或者更深层的对语义上进行转换,使得代码不容易阅读,但对整个代码的逻辑关系和结构没有影响,而且可以借助jd-gui等工具直接将.class文件转换成源码,只要有耐心,弄懂源码设计思路完全没有问题,是相对比较简单的方式,总结起来就是加密让人难读懂,但不影响机器明白。
(2)字节码转换
主要思路是通过加密算法修改.class文件,然后打包成软件产品,运行时调用解密算法对.class文件动态解密,转换成能够被JVM装载识别的二进制字节码,操作起来技术含量较高,也不容易被反编译,是相对比较稳妥的加密方式,总结起来就是加密后让人和机器都读不懂,但给个机器明白的解密算法帮助机器懂;从解密方法实现上来说,又分为以下几种:
a) 定制ClassLoader
主要思路是通过在软件中加入自定义类加载器,集成解密算法,在读取类二进制节码后,先对字节码解密再成类;
b) 利用Instrument
主要思路是通过继承Instrument中的ClassFileTransformer,编写对应的transform方法进行解密,生成相应的类包文件decrypt.jar,使用java -javaagent:decrypt.jar –jar xxx.jar命令启动加密后的软件,通过Premain-Class,向Instrumentation注入ClassFileTransformer实现进行运行时类文件的解密;
c) JVMTI编程
主要思路是通过JVMTI编程,使用C/C++将解密算法封装在decrypt.dll文件中,使用java -agentpath:decrypt.dll –jar xxx.jar命令启动加密后的软件,通过Agent_OnLoad引入ClassFileLoadHook实现运行时类文件的解密。
除以上两大类方法外,还有一些别的方法,例如将软件整体加密,启动前先解密后再运行,也都可以在特定场景下满足一定的源码加密需求,但没有绝对安全不可破的加密方法,我们要做的只是通过一定的手段,增加盗版的成本,尽可能最大程度的对源码进行保护。
本文在项目源码加密方案中使用了JVMTI编程这种方法,所以下文会着重论述这种方法的特点、实现方法及在SpringBoot项目中存在的问题与解决办法。
3. 为什么选择JVMTI方法
3.1 加密方法分析
前面已经讨论过几类加密的方法,下面结合个人理解,从设计思路、技术要求、算法依赖性及效果等方面对上述几种方法的优势与不足进行分析:
(1) 源码混淆
这种方法的主要思路是增加源码阅读障碍,也就是说不影响源码的打开查看,加密等级最低,但从实现来说却需要加密者对.class文件的结构非常熟悉(当然使用第三方组件、工具另论),要能准确识别其中的各类变量、函数、类名等,需建立一套混淆关系表,统一处理各类之间的相互引用、方法及变量的调用等,否则会出现加密后类文件损坏、类引用失败或方法调用错误等问题。
a) 这类方法设计思路相对比较简单,并且已经有很多功能完善的第三方组件、工具,直接使用即可,这也是一大优势。
b) 技术上要求对.class文件内容结构非常熟悉,而且实现起来需要注意的细节多、操作繁琐。
c) 算法上主要是对类变量、函数、方法等名称的处理,技术难度不高;软件对算法的依赖度低,当然这是优点也是缺点,方便自己的同时也方便了别人。
d) 效果上混淆后.class文件内容结构未发生改变,业务逻辑及处理算法等未受到有效保护,保护程度低。
总体来说如果自己要从零开始搞一套的话性价比不高。
(2) 字节码转换
字节码转换的方法比较釜底抽薪,可以从根本上改变.class文件的结构,加密后的文件就是一份普通的二进制文件,甚至给人的感觉是一份损坏的.class文件,在没有解密算法的情况下很难被反编译,可以有效的保护软件源码。
a) 设计思路上这类方法基本相通,都是对加密过的.class文件二进制编码进行解密处理后转再换成具体类,只是实现过程有所不同:定制ClassLoader方式需要手动调用解密方法,而利用Instrument(底层实现也是基于JVMTI)和JVMTI编程则是把这个调用过程交给JVM自动实现。
b) 技术上涉及知识面广,除了对.class文件内容结构有一定了解外,还要对Java类的加载机制原理、自定义类加载器、JNI、JVMTI及JVM运行等有一定了解,技术要求高。
c) 算法上主要是对.class文件的处理,这个过程本身技术要求不高,但算法设计是重点,而且软件对算法依赖度高,缺失解密算法软件产品或不能正常运行。
d) 效果上依实现方式差异而有所不同,前面总结过这类方法目的是加密后让人和机器都读不懂,但给个机器明白的解密算法帮助机器懂,解密算法是关键并且要随软件交付,所以问题也就从对源码的保护进一步转移到了对加解密算法的保护;好的算法固然可以使.class文件难以被反编译,但算法被破解了,所有的加密操作都将功亏一篑,所以这类加密的效果主要依赖于对算法的保护:定制ClassLoader与Instrument使用Java语言实现解密过程,虽然对.class文件进行了加密,但解密过程却被暴露了,对于经验丰富的程序员来说找到相应解密算法,从而破解并反编译出源码只是时间问题;而JVMTI编程使用C/C++语言实现解密过程,然后封装成dll文件,最终由JVM自动调用解密,鉴于C/C++编译型语言的特性,dll文件不容易被破解,所以算法也就能更好的被保护起来,当然这也不是绝对的方法,技术专家可以通过JVM本身或其他手段获取到软件源码,但这需要较高的技术能力和丰富的开发经验。
3.2 选择JVMTI方法
通过分析不难看出,JVMTI编程这种方法对.class加密,不仅能够很好的实现源码的保护,更能使解密算法及解密过程被保护起来,总体来说是目前效果最理想的办法,也是本文后续着重讲述的技术点。
4. JVMTI加密实现
前面已经论述过加密思路,这里直接上图,更直观的说明从生成dll文件到类加解密调用整个过程:
4.1 思路
(1) 使用C++编写加解密算法,并封装成jarencrypt.dll文件,对应上图深蓝色步骤①-⑤;
(2) 编写加密工具调用jarencrypt.dll中的算法实现jar包内.class文件加密,上图红色部分;
(3) 使用加密工具对demo进行测试验证,对应上图浅蓝色步骤①-⑦。
4.2 环境
(1) VisualStdio2012,用于编写算法、封装dll;
(2) IDEA2018(JDK8),用于编写加密工具及测试工程;
(3) SpringBoot(2.3.4.RELEASE),测试工程引用版本,用于后续对SpringBoot工程jar包加密方法的讨论。
4.3 实施
4.3.1 封装jarencrypt.dll
这一步是整个加密方案的核心内容,涉及JNI、JVMTI编程两部分,相关技术细节不再赘述,重点讲述方法的实现步骤: