Spring项目源代码加密

前言

公司项目要求进行源代码加密,防止他人进行反编译(毕竟项目要运行在客户的机器上)。项目框架采用的是:Spring + Spring MVC + Spring Data JPA。可在网上查阅资料,关于Spring项目源代码加密的内容不多,也没找到什么现成的工具。所以,只能自己动手写加密代码了。过程几经坎坷,在此进行记录一下,也希望能帮到有相同需求的朋友。

思路

写工具类手动对项目指定包下生成的class文件内容进行加密,在容器框架加载class文件时解密获取正确的字节码。头疼的来了,不少地方都对字节码文件进行了读取解析,要改不少源代码。

准备工作

1、准备JDK1.8源码(我电脑配置的JDK为1.8.0_131)。在JDK安装目录下有个src.zip,那个就是当前版本JDK的源码

2、准备Tomcat7源码。(需要安装ant进行项目构建)

3、准备Spring源码。这个下载的框架里面自带的

4、准备hibernate-entitymanager源码

5、准备aspectjweaver源码(项目中切面编程做日志) 

5、一个加密解密的工具类jar包。用于对生成的class文件进行加密,在我们修改后的tomcat、spring中需要调用这个类对已加密的class进行解密。另外这个jar包最后需要使用加密锁进行壳加密以保证加解密代码的安全

6、写一个读取配置文件的工具类。配置文件中记录需要解密的包名、路径地址、是否执行解密操作(便于开发时调试)等信息
 

二、需要修改的类 

 

1、JDK中需要修改的类

a)    java.io.FileInputStream:修改后覆盖到rt.jar中对应包里的class文件

2、Tomcat中需要修改的类

a)    org.apache.tomcat.util.bcel.classfile.ClassParser:修改后覆盖到tomcat-coyote.jar中对应包里的class文件

b)    org.apache.catalina.loader.WebappClassLoader:修改后覆盖到catalina.jar中对应包里的class文件

3、Spring中需要修改的类

a)    org.springframework.core.type.classreading.SimpleMetadataReader:修改后覆盖到spring-core-4.3.7.RELEASE.jar中对应包里的class文件

4、hibernate-entitymanager中需要修改的类

a)   org.hibernate.ejb.packaging.AbstractJarVisitor:修改后覆盖hibernate-entitymanager-4.1.8.Final.jar中对应包里的class文件

b)   org.hibernate.ejb.packaging.ExplodedJarVisitor:修改后覆盖hibernate-entitymanager-4.1.8.Final.jar中对应包里的class文件

5、aspectjweaver中需要修改的类

a)   org.aspectj.apache.bcel.classfile.ClassParser:修改后覆盖aspectjweaver.jar中对应包里的class文件

b)   org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository:修改后覆盖aspectjweaver.jar中对应包里的class文件

三、进行修改 

 1、修改JDK提供的java.io.FileInputStream类

目的:提供方法获取class文件的路径,这样才能做后面的解码。

    /**
     * The path of the referenced file
     * (null if the stream is created with a file descriptor)
     */
    private final String path;

    /**
     * 
     * @title: getPath
     * @description: 提供方法让外部能够获取到当前文件路径
     * @author: 陈家宝
     * @version: V1.00
     * @date: 2019年3月11日 下午1:07:44
     * @return
     */
    public String getPath() {
		return path;
	}

 

 2、修改Tomcat的org.apache.tomcat.util.bcel.classfile.ClassParser类

对ClassParser方法进行重写。目的:对配置文件中要求进行解密的class文件经过解密后,再将流给Tomcat。

原方法

public ClassParser(final InputStream inputStream) {
    this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
}

修改后

    public ClassParser(final InputStream inputStream) {
    	// TODO 判断该类是否需要解密
    	InputStream newInputStream = inputStream;
    	
    	try {
			//DecodeConf是记录配置信息的类
			//DecodeConf.isRunDecode:记录是否需要执行解密操作
			if (DecodeConf.getConf().isRunDecode() && inputStream instanceof FileInputStream) {
				// 配置中设置为需要解密,从文件流获取文件路径,用于判断是否为需要解密的类
				// FileInputStream.getPath()为修改JDK源码而来,所有要用我的JDK
				String path = ((FileInputStream)inputStream).getPath();
				FileOperateHelper.log("ClassParser path=" + path, true);
				/*
	               *DecodeConf.dirs:所有需要解密的文件路径(配置文件中记录到目录这层,根据需要可以明确到文件)集合
	               *dirs是一个集合对象,记录了所有需要解密的目录
	               *我配置文件中记录的是相对路径,只到包名这层
	               *如包名为com.abc.service则记录的目录路径为/com/abc/service/
	               *判断当前目录是否需要解密
	              	*/
				for(String dir : DecodeConf.getConf().getDirs()) {
					// 统一文件路径分隔符
					dir = dir.replace("/", "\\");
					FileOperateHelper.log("ClassParser dir=" + dir, true);
					if (path.indexOf(dir) != -1) {
						// 该类在需要解密的目录下,进行解密
						// 读取文件内容
						byte[] oldcontent = FileOperateHelper.read(new File(path));
						// 进行解密
						byte[] decodeByte = EncodeUtil.simpleDecrypt(oldcontent);
						
						newInputStream = new ByteArrayInputStream(decodeByte);
						break;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    	
    	// 不管上面有没有解密,用newInputStream生成DataInputStream
        this.dataInputStream = new DataInputStream(new BufferedInputStream(newInputStream, BUFSIZE));
    }

3、修改Tomcat的org.apache.catalina.loader.WebappClassLoader类

在类中重写下findClass方法

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

    	/**
    	 * 类加载器的三个机制:委托、单一性、可见性
    	 * 委托:指加载一个类的请求交给父类加载器,若父类加载器不可以找到或者加载到,再加载这个类
    	 * 单一性:指子类加载器不会再次加载父类加载器已经加载过的类
    	 * 可见性:子类加载器可以看见父类加载器加载的所有类,而父类加载器不可以看见子类加载器所加载的类
    	 * 
    	 * 所以,return super.findClass(name);是可能加载不到类的(可能有些类需要子类加载器才加载到),
    	 * 即意味着可能会产生ClassNotfoundException异常,所以不能放在try catch代码块里边,因为调用者需要知道是否成功加载。
    	 */
    	// 如果配置文件中配置了不执行解密,直接调用父类的findClass
    	if (!DecodeConf.getConf().isRunDecode()) {
    		return super.findClass(name);
    	}
    	
    	try {
			// TODO 判断当前类是否需要解密
			// 判断当前类是否在需要解密的包路径之下
			for(String pkg : DecodeConf.getConf().getPackages()) {
				if (name.indexOf(pkg) != -1) {
					// 将类名称转换为文件路径及文件名
					String fileName = name.replace(".", "/") + ".class";
					// 根据文件路径,从已加载的类的ResourceEntry集合中获取指定的ResourceEntry
					ResourceEntry resourceEntry = resourceEntries.get("/" + fileName);
					// 拼接class文件绝对路径,当然,ResourceEntry中也有文件的绝对路径
					String basePath = DecodeConf.getConf().getClassPath();
					String classPath = basePath + fileName;
					// 但是,内部类获取不到ResourceEntry(不确定是不是全部的内部类都获取不到),如果获取得到就用ResourceEntry中的路径
					if (resourceEntry != null) {
						// 如果路径中带空格会变成"%20"导致无法成功获取文件,进行处理
						classPath = URLDecoder.decode(resourceEntry.source.getPath(), "UTF-8");
						if (classPath.startsWith("/")) {
							classPath = classPath.substring(1);
						}
					}
					
					File classFile = new File(classPath);
					// 如果文件存在,则进行解密
					if (classFile.exists() && classFile.isFile()) {
						InputStream is = StreamDecode.decode(new FileInputStream(classFile));
						// 将解密后的流包含的内容,读取到字节数组
						byte[] b = new byte[is.available()];
						is.read(b);
						// 将字节数组内容转换成Class对象
						return defineClass(b, 0, b.length);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    	
    	return super.findClass(name);
    }

4、修改Spring的org.springframework.core.type.classreading.SimpleMetadataReader类

原方法

SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
    InputStream is = new BufferedInputStream(resource.getInputStream());
    ClassReader classReader;
    try {
        classReader = new ClassReader(is);
    }
    catch (IllegalArgumentException ex) {
        throw new NestedIOException("ASM ClassReader failed to parse class file - " +
        "probably due to a new Java class file version that isn't supported yet: " + resource, ex);
    }
    finally {
        is.close();
    }
    AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
    classReader.accept(visitor, ClassReader.SKIP_DEBUG);
    this.annotationMetadata = visitor;
    // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
    this.classMetadata = visitor;
    this.resource = resource;
}

修改后

SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
		InputStream is = new BufferedInputStream(resource.getInputStream());
		InputStream newInputStream = is;
		ClassReader classReader;
		try {
			try {
				//TODO 判断是否需要解密
				FileOperateHelper.log("isRunDecode() = " + String.valueOf(DecodeConf.getConf().isRunDecode()), true);
				if(DecodeConf.getConf().isRunDecode()) {
					f
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值