如何使用自定义类加载器防止代码被反编译破解
前言
最近和朋友聊天,他接了个外包项目,他问我有没有办法让自己的源码不被反编译破解,我就跟他说可以对代码进行混淆和加密。今天我们就来聊聊如何通过对代码进行加密实现代码防反编译,至于混淆因为可以直接利用proguard-maven-plugin进行配置实现,相对比较简单,就不在本文论述
代码防编译整体套路
1、编写加密工具类
@Slf4j public class EncryptUtils { private static String secretKey = "test123456lyb-geek"+System.currentTimeMillis(); private EncryptUtils(){} public static void encrypt(String classFileSrcPath,String classFileDestPath) { System.out.println(secretKey); FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(classFileSrcPath); fos = new FileOutputStream(classFileDestPath); int len; String[] arrs = secretKey.split("lyb-geek"); long key = Long.valueOf(arrs[1]); System.out.println("key:"+key); while((len = fis.read())!=-1){ byte data = (byte)(len + key + secretKey.length()); fos.write(data); } } catch (Exception e) { log.error("encrypt fail:"+e.getMessage(),e); }finally { if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2、对需要防止被反编译代码加密
public static void main(String[] args) { String classFileSrcPath = classFileSrcPath("UserServiceImpl"); System.out.println("classFileSrcPath:--->"+classFileSrcPath); String classFileDestDir = ServiceGenerate.class.getClassLoader().getResource("META-INF/services/").getPath(); System.out.println("classFileDestDir:--->"+classFileDestDir); String classFileDestPath = classFileDestDir + "com.github.lybgeek.user.service.impl.UserServiceImpl.lyb"; EncryptUtils.encrypt(classFileSrcPath,classFileDestPath); }
3、对加密代码进行反编译验证
打开反编译工具jd-gui,把加密的代码拖入jd-gui
打不开,至少说明不能用jd-gui来反编译加密过的代码。
我们打开正常的编译的class文件,其内容形如下 从内容我们大概还是能看出一些东西,比如包名啥的。而打开加密后的文件,其内容如下
内容宛若天书
思考一:代码都被加密了,那jvm如何识别?
答案:既然有加密,自然可以通过解密来使用。那这个解密得存放在什么地方进行解密?
如果对类加载有一定了解的朋友,就会知道java的class文件是通过类加载器把class加载入jvm内存中,因此我们可以考虑把解密放在类加载器中。常用的类加载有启动类加载器、扩展类加载器、系统类加载。我们正常classpath路径下的类都是通过系统类加载器进行加载。而不巧这三个jdk提供的加载器没法满足我们的需求。因此我们只能自己实现我们的类加载器。其自定义加载器代码如下
@Slf4j public class CustomClassLoader extends ClassLoader{ /** * 授权码 */ private String secretKey; private String SECRETKEY_PREFIX = "lyb-geek"; /** * class文件的根目录 */ private String classRootDir = "META-INF/services/"; public CustomClassLoader(String secretKey) { this.secretKey = secretKey; } public String getClassRootDir() { return classRootDir; } public void setClassRootDir(String classRootDir) { this.classRootDir = classRootDir; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clz = findLoadedClass(name); //先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。 if(clz != null){ return clz; }else{ ClassLoader parent = this.getParent(); clz = getaClass(name, clz, parent); if(clz != null){ return clz; }else{ clz = getaClass(name); } } return clz; } private Class<?> getaClass(String name) throws ClassNotFoundException { Class<?> clz; byte[] classData = getClassData(name); if(classData == null){ throw new ClassNotFoundException(); }else{ clz = defineClass(name, classData, 0,classData.length); } return clz; } private Class<?> getaClass(String name, Class<?> clz, ClassLoader parent) { try { //委派给父类加载 clz = parent.loadClass(name); } catch (Exception e) { //log.warn("parent load class fail:"+ e.getMessage(),e); } return clz; } private byte[] getClassData(String classname){ if(StringUtils.isEmpty(secretKey) || !secretKey.contains(SECRETKEY_PREFIX) || secretKey.split(SECRETKEY_PREFIX).length != 2){ throw new RuntimeException("secretKey is illegal"); } String path = CustomClassLoader.class.getClassLoader().getResource("META-INF/services/").getPath() +"/"+ classname+".lyb"; InputStream is = null; ByteArrayOutputStream bas = null; try{ is = new FileInputStream(path); bas = new ByteArrayOutputStream(); int len; //解密 String[] arrs = secretKey.split(SECRETKEY_PREFIX); long key = Long.valueOf(arrs[1]); // System.out.println("key:"+key); while((len = is.read())!=-1){ byte data = (byte)(len - key - secretKey.length()); bas.write(data); } return bas.toByteArray(); }catch(Exception e){ e.printStackTrace(); return null; }finally{ try { if(is!=null){ is.close(); } } catch (IOException e) { log.error("encrypt fail:"+e.getMessage(),e); } try { if(bas!=null){ bas.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
通过如下方式进行调用
public static void main(String[] args) throws Exception{ CustomClassLoader customClassLoader = new CustomClassLoader("test123456lyb-geek1603895713759"); Class clz = customClassLoader.loadClass("com.github.lybgeek.user.service.impl.UserServiceImpl"); if(clz != null){ Method method = clz.getMethod("list", User.class); method.invoke(clz.newInstance(),new User()); } }
思考二:通过自定义加载器加载过的类如何整合进行spring?
答案: 通过spring提供的扩展点进行ioc容器注入
1、编写bean定义,并注册注册bean定义
@Component public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { Object secretKey = YmlUtils.getValue("lyb-geek.secretKey"); if(ObjectUtils.isEmpty(secretKey)){ throw new RuntimeException("secretKey can not be null,you maybe need to config in application.yml with key lyb-geek.secretKey"); } registerBean(beanFactory,secretKey.toString()); // setClassLoader(beanFactory,secretKey.toString()); } /** * 如果项目中引入了>spring-boot-devtools,则默认加载器为org.springframework.boot.devtools.restart.classloader.RestartClassLoader * 此时如果使用自定加载器,则需把bean的类加载器变更为AppClassLoader * @param beanFactory */ private void setClassLoader(ConfigurableListableBeanFactory beanFactory,String secretKey) { CustomClassLoader customClassLoader = new CustomClassLoader(secretKey); beanFactory.setBeanClassLoader(customClassLoader.getParent()); } private void registerBean(ConfigurableListableBeanFactory beanFactory,String secretKey){ DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(UserService.class); GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); definition.getPropertyValues().add("serviceClz",UserService.class); definition.getPropertyValues().add("serviceImplClzName", "com.github.lybgeek.user.service.impl.UserServiceImpl"); definition.getPropertyValues().add("secretKey", secretKey); definition.setBeanClass(ServiceFactoryBean.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); String beanId = StringUtils.uncapitalize(UserService.class.getSimpleName()); defaultListableBeanFactory.registerBeanDefinition(beanId, definition); } }
2、如果是接口注入,还需通过FactoryBean进行狸猫换太子
@Data @AllArgsConstructor @NoArgsConstructor public class ServiceFactoryBean <T> implements FactoryBean<T> , ApplicationContextAware, InitializingBean { private ApplicationContext applicationContext; private Class<T> serviceClz; private String serviceImplClzName; private String secretKey; private Object targetObj; @Override public T getObject() throws Exception { return (T) targetObj; } @Override public Class<?> getObjectType() { return serviceClz; } @Override public void afterPropertiesSet() throws Exception { targetObj = ServiceFactory.create(secretKey,serviceImplClzName,applicationContext); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
3、验证是否整合成功
验证示例代码
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping(value = "/save") public User save(User user){ User newUser = userService.save(user); return newUser; } }
能够正常输出,说明整合成功
总结
上述的例子只是提供一个思路,并不能完全杜绝代码被反编译。因为如果真想进行反编译,其实可以先通过反编译你自定义的类加载器,然后通过解密方式,去逆推加密算法,从而还原加密类。要杜绝代码被反编译的思路有如下
-
提高反编译的成本,比如对自定义类加载再次加密,编写复杂的加密算法
-
编写让人没有欲望反编译的代码,比如写一堆垃圾代码
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-code-authorization

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Debian9 下卸载 MariaDB 并安装 Mysql8
因为部分字段需要使用json格式,所以需要MySql 8! linux:Debian 9 mysql:8.0.13 完全卸载mariaDB sudo apt-get purge mariadb-* 这是卸载mysql sudo apt-get purge mysql-\* 弹出保留 MariaDB 数据库的提示,是彻底地卸载的话,一并移除即可。 卸载的时候可能会有各种问题,有的问题可能需要直接杀死进程。 # 查看进程 ps aux | grep amoeba # 杀死进程 kill -s 9 pid 安装mysql 首先,将 MySQL APT 存储库添加到系统,先到MySQL存储库下载页面,并使用以下wget命令下载最新的发行包: wget http://repo.mysql.com/mysql-apt-config_0.8.13-1_all.deb 下载完成后,通过以下命令安装: sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb 你将会看到MySQL配置安装菜单,选择你要安装的版本。 默认选择了 MySQL 8.0,如果要安装 MySQL...
- 下一篇
开源软件基金会的崛起与演化
开源之道引言 本文翻译于一篇基于CC-By协议的论文,该论文最初发表在《International Free and Open Source Software Law Review》Vol. 5, Issue 1 中,作者是 Paula Hunter 和Stephen Walli。 开源之道打算在2020年的3月份花一整个月的时间来整理关于开源软件基金会的资料,这篇算是开篇,外加前几年的累积,预计会在月底有一个较为宽广的视野,起码让人有路径可循,如果能够回答“开源基金会为什么会存在?能够帮助人们干什么?”这个问题,那就真是万幸了。 摘要 自由和开源软件(FOSS)项目共同体(Community)仍然保持着增长和爆发的上升姿态。当开源项目的增长达到某个临界点的时候,就会有一些企业意图想要参与,而企业有大量的软件知识产权相关的内容需要管理,然而,这些却不是项目本身所擅长的。那么中立的、非营利性质的 FOSS 基金会就是提供这些问题的解决方案的,通过提供知识产权的管理、业务和技术方面的服务,进一步帮助项目和共同体的成长。本文重点回顾了中立、非营利性的组织是如何发展的,以及它为何...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS关闭SELinux安全模块