栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

如何使用自定义类加载器防止代码被反编译破解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何使用自定义类加载器防止代码被反编译破解

前言

最近和朋友聊天,他接了个外包项目,他问我有没有办法让自己的源码不被反编译破解,我就跟他说可以对代码进行混淆和加密。今天我们就来聊聊如何通过对代码进行加密实现代码防反编译,至于混淆因为可以直接利用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";


    
    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());
    }

    
    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  implements FactoryBean , ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;

    private Class 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

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号