背景
在以往很多商业的系统,除了知识产权以外很多代码都是加密所以我们很难去直接去读取原码,并且这样通过加密.class文件有效的对自已的产品或系统进行保护。
实现原理
生成.class后将原来的.class进行加密或者取反,因为.class里面最终生成的是二进制0101这类的二进制代码,当然也可以通过一些md5或一些RES等加密方式进行加密;以下案例是参考网上一些案例而来,参考文章在文末,其实原理一样。解密的时候通过去实现java的classLoader将原来的.findClass 进行改造,就可以实现针对性的加密(tomcat实现打破双亲委派也是这样的哦~),其实很简单参考如下:
代码下载地址:https://gitee.com/hong99/jdk8.git
实现代码
package com.encryption.demo;
/**
* @description: 测试方法
* @author hong
* @date
* @version 1.0
*/
public class TestContent {
public void hong(){
System.out.println("my name is hong!!!");
}
}
package com.encryption.demo;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 加解密工具
*/
public class EncryptionUtils extends ClassLoader{
/**统一加密/解密 密钥 **/
private int password = 0xFF;
private static String parentPath ="D://";
/**
* 加密方法
* @param pathUrl 类名称
*/
public void encryptionClass(String pathUrl){
if(null==pathUrl || "".equals(pathUrl)){
System.out.println("路径不能为空!");
return;
}
String path =new File(pathUrl).getAbsolutePath();
//获取文件
File file = new File(path);
if(!file.exists()){
throw new NoClassDefFoundError("没有找到文件!");
}
//获取文件夹目录
File awaitFileFolder = new File(file.getParent() + File.separator);
//不存在则创建目录
if(!awaitFileFolder.exists()){
awaitFileFolder.mkdirs();
}
File encryptFolder = new File(awaitFileFolder.getParent() + File.separator );
if (!encryptFolder.exists()) {
encryptFolder.mkdirs();
}
//加密后文件指定存放目录
// String encryptedFile = file.getParent() + File.separator + File.separator + file.getName();
String encryptedFile = parentPath + file.getName();
try (
FileInputStream fileInputStream = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)
) {
// 加密
int data;
while ((data = bis.read()) != -1) {
bos.write(data ^ password);
}
bos.flush();
} catch (IOException e) {
System.out.println("加密异常!");
e.printStackTrace();
}
// 现在将原来未加密的文件删除
// awaitFileFolder.delete();
// 先获取加密前文件绝对路径名,然后修改后缀,再创建File对象
File oldFile = new File(parentPath +file.getName()+ "hong");
// 删除未加密前的文件
// if (oldFile.exists()) {
// oldFile.delete();
// }
// 根据加密后的文件创建File对象
File newEncryptedFile = new File(encryptedFile);
// 将加密后的对象重命名,这时加密后的文件就把加密前的文件替换掉了,这就是为什么刚开始加密后的文件需要单独放的原因
newEncryptedFile.renameTo(oldFile);
// 删除之前的加密文件夹下面的加密文件
newEncryptedFile.getParentFile().delete();
}
/**
* 解密方法
* @param pathUrl 需要解密的文件名
*/
protected byte[] decryptClass(String pathUrl) {
String path;
if (!pathUrl.contains(".class")) {
path = new File(pathUrl).getAbsolutePath();
} else {
path = pathUrl;
}
//获取待解密文件
File decryptClassFile = new File(path);
if (!decryptClassFile.exists()) {
System.out.println("decryptClass() File:" + path + " not found!");
return null;
}
byte[] result = null;
BufferedInputStream bis = null;
ByteArrayOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(decryptClassFile));
bos = new ByteArrayOutputStream();
// 解密
int data;
while ((data = bis.read()) != -1) {
bos.write(data ^ password);
}
bos.flush();
result = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = decryptClass(name);
int length = bytes ==null?0:bytes.length;
return defineClass("com.encryption.demo.TestContent",bytes,0,length);
}
/**
* 运行主类
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
//获取项目路劲
File file = new File(EncryptionUtils.class.getResource("/").getPath());
//加解密工具
EncryptionUtils encryptionUtils = new EncryptionUtils();
//解析路劲
encryptionUtils.encryptionClass(file.getPath()+"/com/encryption/demo/TestContent.class");
//获取解析后的文件
Class<?> c = encryptionUtils.findClass(parentPath+"/TestContent.classhong");
if(c != null){
try {
Object obj = c.newInstance();
//获取方法
Method method = c.getDeclaredMethod("hong");
//通过反射调用Test类的say方法
method.invoke(obj);
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
结果
Connected to the target VM, address: '127.0.0.1:49636', transport: 'socket'
my name is hong!!!
Disconnected from the target VM, address: '127.0.0.1:49636', transport: 'socket'
Process finished with exit code 0
最后
看过很多实现的方法大同小异,主要是用来学习,以备将来在一些特定场景上面会使用到。当然以上太简单了,如果不懂类加载机制的同学可以参考以往文章。(最近疫情有点反弹,出门注意安全防护。祝各位,一切顺利,未来可期!!!)
参考文章: