Spring加载加密后的文件,防止反编译
2016-12-07 17:50
253 查看
公司的产品需要放在合作伙伴的产品里面部署到客户那边,为了防止他方很方便地反编译我们产品,需要对其进行保护。
网上有免费的如ProGuard,收费的有 Zelix 等,公司也购买了一个加密狗什么的。公司总是希望不花钱或者花很少的钱完成某种程度的保护,甚至有点是防君子不防小人了。免费的ProGuard只是混淆工具,而且不能进行Flow Obfuscation,且不说人家Debug就能厘清代码调用逻辑,混淆后Spring容器压根就没办法起来,Spring太依赖反射了。公司的加密狗也因同一的原因不能加密Spring管理的代码。
现在几乎只能寄希望于程序员来写加密代码了,我的思路是这样的:
1. 将工程包解压后,选取工程里面若干个核心的代码进行加密,然后把加密的文件放在一个特有的路径上;同时删除被加密的.class文件。
2. 写一个解密程序,该程序拿到加密的密钥,和加密后的文件路径,逐一解密并且把文件写到它应当出现的地方。
3. 使用Spring API 将这些解密过的文件注册到 Spring 容器并实例化,然后删除这些解密的文件。
其中第 2, 3 步都是写在解密程序里面的,保证加密的文件->解密->被Spring管理这三大步骤都是在Spring容器启动的时候完成的,不给他方机会去获得解密的.class文件。
当然,最最重要的部分是我的解密程序必须被公司的加密狗加密。
当前只是测试通过,还没有优化代码,很多文件路径都使用的是绝对路径。还有就是加密解密的代码都是网上拷贝的,之所以写在这里,是给自己做笔记。
1. 加密解密的密钥:
2. 加密需要被加密的核心.class文件,这个需要线下做了,也就是上面3个步骤中的第一步,简单点就直接main方法走起:
3. 解密文件,这个文件是希望被Spring管理,并且在加载并实例化结束的时候就执行解密操作,包括注册并实例化解密后的.class文件到Spring容器,完了删除硬盘上的解密后的文件,擦掉痕迹,不能让他方能够找到解密后的文件并反编译。
在写这个博客的时候,还没有使用公司的加密狗加密这个解密程序并测试是否能工作。但是如果不行,只能希望 F:/des_key2.xml 里面的密钥可以被加密。
UserController.class被加密后的文件名,我使用的是UserControllerEncrypted.class,但它实际上已经是一个MalFormed的二进制文件,所以tomcat在启动的时候会报错:
但是这个不影响 tomcat 的启动,Srping 会起来的,并且会加载并实例化 Bean 。期间还碰到一个问题,有没有加密解密过,@PathVariable 的使用会不同的。
没有被加密过,下面的方法签名和Spring的注解联合使用没问题:
但是加密过,并且解密后,他是不能工作的,会报下面的错误:
修改下注解的使用方式,增加一个 value 属性给 @PathVariable 就可以工作了,暂时没搞清楚为什么。
==========================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的上面。
对应的DecryptionListener 程序:
公司的加密狗也有诸多限制,就是被加密的 contextInitialized 里面不能有 Exception 的处理,所以把Exception的处理挪到了decrypt() 方法里。
为了解密后的.class文件尽快被删除,我就把删除代码写到了对应的java文件里,使用 @PostConstruct 注解让其在Spring 实例化完之后马上执行。
网上有免费的如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 () ); } }
2. 加密需要被加密的核心.class文件,这个需要线下做了,也就是上面3个步骤中的第一步,简单点就直接main方法走起:
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; @Component public class Decryption { private static final Logger logger = Logger.getLogger(Decryption.class); @PostConstruct public 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.processAnnotationsWebR esource 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) @ResponseBody public BaseResponse <Map<String, List<LinkedHashMap<String, LinkedHashMap<String, String>>>>> getPersonSummary(@PathVariable Integer personId){
但是加密过,并且解密后,他是不能工作的,会报下面的错误:
[org.springframework.core.LocalVariableTableParameterNameDiscoverer]-[DEBUG] Cannot find '.class' fi le for class [class <package>.UserController] - unable to determine constructor/method parameter names
修改下注解的使用方式,增加一个 value 属性给 @PathVariable 就可以工作了,暂时没搞清楚为什么。
@RequestMapping(value="{personId}/summary",method=RequestMethod.GET) @ResponseBody public 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); @Override public 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 ."); } @Override public 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 { @PostConstruct public 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加载的Properties文件
- spring-cloud-config 非对称加密 keystore 文件加载异常
- 加密Spring加载的Properties文件
- 加密Spring加载的Properties文件
- Spring加载Properties配置文件的加密解密处理
- 加密Spring加载的Properties文件
- Java加密Jar包和Class文件防止反编译的方法
- Spring加载加密的配置文件详解
- JAVA程序保护方案(JAVA加密保护,防止反编译、防拷贝)
- spring 中加载xml配置文件的方式.
- spring 中加载xml配置文件的方式.
- 加载spring配置文件的常用三种方法
- 加载spring配置文件的常用三种方法
- spring 中加载xml配置文件的方式
- Spring使用通配符自动加载hibernate映射文件生成sessionFactory
- spring加载多个配置文件
- Spring 中加载资源文件
- spring配置文件加载
- web.xml文件中配置(servlet, spring, filter, listenr)的加载顺序 研究
- Spring配置文件加载时出现Bean property 'newscontentDAO' is not writable or has an invalid setter method错误的解决[00原创]