Spring加载加密后的文件,防止反编译
来源:互联网 发布:ubuntu anaconda安装 编辑:程序博客网 时间:2024/06/05 14:36
公司的产品需要放在合作伙伴的产品里面部署到客户那边,为了防止他方很方便地反编译我们产品,需要对其进行保护。
网上有免费的如ProGuard,收费的有 Zelix 等,公司也购买了一个加密狗什么的。公司总是希望不花钱或者花很少的钱完成某种程度的保护,甚至有点是防君子不防小人了。免费的ProGuard只是混淆工具,而且不能进行Flow Obfuscation,且不说人家Debug就能厘清代码调用逻辑,混淆后Spring容器压根就没办法起来,Spring太依赖反射了。公司的加密狗也因同一的原因不能加密Spring管理的代码。
现在几乎只能寄希望于程序员来写加密代码了,我的思路是这样的:
1. 将工程包解压后,选取工程里面若干个核心的代码进行加密,然后把加密的文件放在一个特有的路径上;同时删除被加密的.class文件。
2. 写一个解密程序,该程序拿到加密的密钥,和加密后的文件路径,逐一解密并且把文件写到它应当出现的地方。
3. 使用Spring API 将这些解密过的文件注册到 Spring 容器并实例化,然后删除这些解密的文件。
其中第 2, 3 步都是写在解密程序里面的,保证加密的文件->解密->被Spring管理这三大步骤都是在Spring容器启动的时候完成的,不给他方机会去获得解密的.class文件。
当然,最最重要的部分是我的解密程序必须被公司的加密狗加密。
当前只是测试通过,还没有优化代码,很多文件路径都使用的是绝对路径。还有就是加密解密的代码都是网上拷贝的,之所以写在这里,是给自己做笔记。
1. 加密解密的密钥:
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.security.SecureRandom;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;public class GenerateKey {Path keyFilename = Paths.get("F:/des_key2.xml");public void generateKeyFile() throws Exception {// 生成一个可信任的随机数源SecureRandom sr = new SecureRandom();// 为我们选择的DES算法生成一个KeyGenerator对象KeyGenerator kg = KeyGenerator.getInstance ("DES" );kg.init (sr);// 生成密钥SecretKey key = kg.generateKey();// 将密钥数据保存为文件供以后使用,其中key Filename为保存的文件名Files.write(keyFilename, key.getEncoded () );}}
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.security.SecureRandom;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;public class EncryptionDemo {public void encryptFile(Path keyFilename, Path filename) throws Exception{// 产生一个可信任的随机数源SecureRandom sr = new SecureRandom();//从密钥文件key Filename中得到密钥数据byte rawKeyData [] = Files.readAllBytes(keyFilename);// 从原始密钥数据创建DESKeySpec对象DESKeySpec dks = new DESKeySpec(rawKeyData);// 创建一个密钥工厂,然后用它把DESKeySpec转换成Secret Key对象SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES" );SecretKey key = keyFactory.generateSecret( dks );// Cipher对象实际完成加密操作Cipher cipher = Cipher.getInstance( "DES" );// 用密钥初始化Cipher对象cipher.init( Cipher.ENCRYPT_MODE, key, sr );// 通过读类文件获取需要加密的数据byte data [] = Files.readAllBytes(filename);// 执行加密操作byte encryptedClassData [] = cipher.doFinal(data );// 保存加密后的文件,覆盖原有的类文件。 Files.write( Paths.get("<Absolute_Directory>/UserControllerEncrypted.class"), encryptedClassData );}public static void main(String[] args) throws Exception {EncryptionDemo instance = new EncryptionDemo();instance.encryptFile(Paths.get("F:/des_key2.xml"), Paths.get("<Target_Directory>/UserController.class"));System.out.println("done");}}
3. 解密文件,这个文件是希望被Spring管理,并且在加载并实例化结束的时候就执行解密操作,包括注册并实例化解密后的.class文件到Spring容器,完了删除硬盘上的解密后的文件,擦掉痕迹,不能让他方能够找到解密后的文件并反编译。
import java.nio.file.Files;import java.nio.file.Paths;import java.security.SecureRandom;import javax.annotation.PostConstruct;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;import org.apache.log4j.Logger;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.support.AbstractBeanDefinition;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.beans.factory.support.RootBeanDefinition;import org.springframework.stereotype.Component;@Componentpublic class Decryption {private static final Logger logger = Logger.getLogger(Decryption.class);@PostConstructpublic void decrypt() {System.out.println("开始解密文件。。。。。。。");try {SecureRandom sr = new SecureRandom();byte rawKeyData[] = Files.readAllBytes( Paths.get("F:/des_key2.xml") );DESKeySpec dks = new DESKeySpec (rawKeyData);SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES" );SecretKey key = keyFactory.generateSecret( dks );Cipher cipher = Cipher.getInstance( "DES" );cipher.init( Cipher.DECRYPT_MODE, key, sr );// 获得经过加密的数据byte encryptedData [] = Files.readAllBytes(Paths.get("<Absolute_Diretory>/UserControllerEncrypted.class"));//执行解密操作byte decryptedData [] = cipher.doFinal( encryptedData );System.out.println("解密文件并写到硬盘");Files.write( Paths.get("<Target_Directory>/UserController.class"), decryptedData );System.out.println("进入UserController.class的解密并注册到Spring容器里来。。。。。");DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();AbstractBeanDefinition user = new RootBeanDefinition(UserController.class);user.setScope(BeanDefinition.SCOPE_SINGLETON); //将bean定义注册到容器中beanRegistry.registerBeanDefinition("user", user);System.out.println("成功注册UserController.class到Spring容器里。。。。。");logger.debug("删除解密后的文件");Files.delete(Paths.get("<Target_Directory>/UserController.class"));} catch (Exception e) {throw new RuntimeException("解密文件报错。");} }}
在写这个博客的时候,还没有使用公司的加密狗加密这个解密程序并测试是否能工作。但是如果不行,只能希望 F:/des_key2.xml 里面的密钥可以被加密。
UserController.class被加密后的文件名,我使用的是UserControllerEncrypted.class,但它实际上已经是一个MalFormed的二进制文件,所以tomcat在启动的时候会报错:
07-Dec-2016 16:51:38.626 SEVERE [localhost-startStop-1] org.apache.catalina.startup.ContextConfig.processAnnotationsWebResource Unable to process web resource [/WEB-INF/classes/UserControllerEncrypted.class] for annotations org.apache.tomcat.util.bcel.classfile.ClassFormatException: It is not a Java .class file at org.apache.tomcat.util.bcel.classfile.ClassParser.readID(ClassParser.java:202) at org.apache.tomcat.util.bcel.classfile.ClassParser.parse(ClassParser.java:80) at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2042) at org.apache.catalina.startup.ContextConfig.processAnnotationsWebResource(ContextConfig.java:1940) at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1147) at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:779) at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:306) at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:95) at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5202) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717) at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1092) at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1834) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
但是这个不影响 tomcat 的启动,Srping 会起来的,并且会加载并实例化 Bean 。期间还碰到一个问题,有没有加密解密过,@PathVariable 的使用会不同的。
没有被加密过,下面的方法签名和Spring的注解联合使用没问题:
@RequestMapping(value="{personId}/summary",method=RequestMethod.GET)@ResponseBodypublic BaseResponse <Map<String, List<LinkedHashMap<String, LinkedHashMap<String, String>>>>> getPersonSummary(@PathVariable Integer personId){
但是加密过,并且解密后,他是不能工作的,会报下面的错误:
[org.springframework.core.LocalVariableTableParameterNameDiscoverer]-[DEBUG] Cannot find '.class' file for class [class <package>.UserController] - unable to determine constructor/method parameter names
修改下注解的使用方式,增加一个 value 属性给 @PathVariable 就可以工作了,暂时没搞清楚为什么。
@RequestMapping(value="{personId}/summary",method=RequestMethod.GET)@ResponseBodypublic BaseResponse <Map<String, List<LinkedHashMap<String, LinkedHashMap<String, String>>>>> getPersonSummary(@PathVariable("personId") Integer personId){
==========================2016/12/08 更新=======================
我们的程序是 Spring + Maven Module 化实现的,核心逻辑还是放在Service层,所以必须对核心的Service加密。但是 Service 会被其他Service和Controller依赖,不 像Controller 那么简单,Service之间的依赖关系决定着Spring 对 Bean 的加载顺序。假使我需要对 BService 进行加密,而AService依赖BService,按照上面的实现方案,可能AService先于Decryption类被Spring加载,必然找不到需要的BService。
我实在找不到一个办法,能让Decryption先于所有其他的Service先被Spring发现并实例化。只能找到一个插入点,那就是Tomcat启动后,Spring启动前。于是我在 web.xml 里面注册一个 ServletContextListener ,这个listener 里面写解密程序,并且注册在Spring的ContextLoaderListener的上面。
<listener><listener-class><Package_Name>.DecryptionListener</listener-class></listener> <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
对应的DecryptionListener 程序:
public class DecryptionListener implements ServletContextListener {private static final Logger logger = Logger.getLogger(DecryptionListener.class);@Overridepublic void contextInitialized(ServletContextEvent sce) {logger.debug("---------------------------contextInitialized in DecryptionListener");decrypt("<KEY_STR>", "<Project_Deploy_Dir>/WEB-INF/classes/CDServiceEncrypted.dat", "<Project_Deploy_Dir>/WEB-INF/classes/<package>/CDService.class");logger.debug("------------------------contextInitialized end .");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {logger.debug("contextDestroyed in AAADecryption");}public void decrypt(String keyStr, String fileInput, String fileOutput) {try {DESKeySpec dks = new DESKeySpec(keyStr.getBytes());SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES" );SecretKey key = keyFactory.generateSecret( dks );Cipher cipher = Cipher.getInstance( "DES" );cipher.init( Cipher.DECRYPT_MODE, key );FileInputStream fis2 = new FileInputStream(fileInput);FileOutputStream fos2 = new FileOutputStream(fileOutput);CipherOutputStream cos = new CipherOutputStream(fos2, cipher);doCopy(fis2, cos);logger.debug("FINISH DECRYPTION.......................");} catch (Exception e) {throw new RuntimeException("error when decrypt file.");}}public static void doCopy(InputStream is, OutputStream os) throws IOException {byte[] bytes = new byte[64];int numBytes;while ((numBytes = is.read(bytes)) != -1) {os.write(bytes, 0, numBytes);}os.flush();os.close();is.close();}}
公司的加密狗也有诸多限制,就是被加密的 contextInitialized 里面不能有 Exception 的处理,所以把Exception的处理挪到了decrypt() 方法里。
为了解密后的.class文件尽快被删除,我就把删除代码写到了对应的java文件里,使用 @PostConstruct 注解让其在Spring 实例化完之后马上执行。
public class CDService {@PostConstructpublic void destoryClass() {try {Files.delete(Paths.get("<Project_Deploy_Dir>/WEB-INF/classes/<package>/CDService.class"));} catch (IOException e) {throw new RuntimeException("error when delete class file.");}}
- Spring加载加密后的文件,防止反编译
- 针对java加密防止反编译的办法
- 加密Spring加载的Properties文件
- 加密Spring加载的Properties文件
- Java加密Jar包和Class文件防止反编译的方法
- [Erlang]如何防止beam文件的反编译
- 防止Class类反编译的方法,对Class进行加密
- apk文件防止反编译
- 防止apktook 反编译后打包
- JAVA防止反编译(JAVA加密锁)
- unity 加密、防止反编译、mono编译
- 使用jvmti实现class加密,防止反编译
- JAVA .class 文件防止反编译。
- JAVA .class 文件防止反编译
- JAVA .class 文件防止反编译。
- 防止反编译所用到的proguard.cfg文件的编写
- 使用反编译后的so文件
- Android Studio 基于NDK加密,防止反编译获取加密key
- jquery操作大全筛选dom事件动画表单等
- 半透明渲染新技术摘录
- ThreadUtils
- Codeforces 724B
- 2017年微商方向选择
- Spring加载加密后的文件,防止反编译
- 利用AJAX重写,解决session超时,ajax跳转问题
- 稳定排序和不稳定排序
- ACCP C#Windows 第三章课后5
- 盒子模型
- Android结构
- 编译+反编译+去掉jd-gui行号
- 《nodejs实战》读书笔记
- 图片根据DIV大小等比例缩放