原理就是 生成项目时将待加密的java class文件通过加密算法转换生成加密的二进制文件,此文件不会被JD-GUI等反编译工具直接解密。 项目在启动时,用自定义的ClassLoader将加密的二进制文件进行解密并载入到jvm中,通过反射实例化该java类(最好单例),其他代码就可以调用它的方法了。
1. 比如 待加密的java类命名为 CAU.java,到时生成加密的二进制文件改名为CAU.lib,可以让人很难想到这个lib其实是个加密的class文件。
先定义一个java接口类,让待加密的CAU类实现该接口,也可以再定义个Factory类,项目中的其他代码只引用该接口,调用DemoFactory的getRunInter方法得到RunInter实例,而DemoFactory的setRunInter在生成的项目中是无法直接找到的。
演示项目是maven项目,RunInter和DemoFactory是在src/main/java里,而CAU.java是放在src/test/java(生成的项目包里不会有CAU.class,只有CAU.lib)。
/**
* 待加密类接口
* @author hxt
*
*/
public interface RunInter {
public void runMethod(String str);
}
/**
* 工厂类
* @author hxt
*
*/
public class DemoFactory {
private static RunInter runInter;
public static RunInter getRunInter() {
return runInter;
}
// 加密类中传入
public static void setRunInter(RunInter runInter) {
DemoFactory.runInter = runInter;
}
}
/**
* 被加密的类,生成的项目里是CAU.lib
* @author hxt
*
*/
public class CAU implements RunInter{
public CAU(){
// 将RunInter实例传入DemoFactory
DemoFactory.setRunInter(this);
}
@Override
public void runMethod(String str) {
System.out.println("run str: "+str);
}
}
2. 生成密钥key文件
/**
* 生成密钥key文件,这里使用des算法加密
* @throws Exception
*/
//@Test
public void testGenerateKey() throws Exception {
String keyFileName ="key";
String algorithm = "DES";
String savePath="src/main/resources/";
/* 生成密钥 */
SecureRandom secureRandom = new SecureRandom();
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
keyGenerator.init(secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
/* 将密钥数据保存到文件 */
FileUtil.byteArrayWriteToFile(savePath+keyFileName, secretKey.getEncoded());
}
3. 将CAU.class 转化成加密的CAU.lib
/**
* 将class文件 转化成加密的class
* @throws Exception
*/
@Test
public void testEncryptClass() throws Exception {
String classPath="target/test-classes/";
String savePath="src/main/resources/";
String[] args=new String[]{"CAU.class"}; // 待加密的class
String keyFileName = "key"; //密钥key文件
String algorithm = "DES";
/* 生成密钥 */
SecureRandom secureRandom = new SecureRandom();
byte rawKey[] = FileUtil.fileReadToByteArray(savePath+keyFileName);
DESKeySpec desKeySpec = new DESKeySpec(rawKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
/* 创建用于实际加密的Cipher对象 */
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, secureRandom);
/* 加密命令行中指定的每一类 */
for (int i = 0; i < args.length; i++)
{
String fileName = args[i];
/* 读入类文件 */
byte classData[] = FileUtil.fileReadToByteArray(classPath+fileName);
/* 加密类文件 */
byte encryptedClassData[] = cipher.doFinal(classData);
String saveFileName=fileName.replace(".class", ".lib");
/* 保存加密后的文件 */
FileUtil.byteArrayWriteToFile(savePath+saveFileName, encryptedClassData);
System.out.println("***Encrypted " + saveFileName + " ***");
}
}
4.
自定义的ClassLoader类
/**
* 通过ClassLoader将加密的class还原载入
* @author hxt
*
*/
public class DecryptClassLoader extends ClassLoader
{
private SecretKey key;
private Cipher cipher;
private String path;
private String keyFilename; //密钥文件
/**
* 构造函数:设置解密所需要的对象
*
* @throws GeneralSecurityException
* @throws IOException
*/
public DecryptClassLoader(String keyFilename) throws Exception
{
this.keyFilename=keyFilename;
//this.path=DecryptClassLoader.class.getResource("/").getPath();
this.path="/";
//InputStream is=DecryptClassLoader.class.getResourceAsStream("/key");
init();
}
/**
* 构造函数:设置解密所需要的对象
*
* @throws GeneralSecurityException
* @throws IOException
*/
public DecryptClassLoader(String keyFilename,String path) throws Exception
{
//path=DecryptClassLoader.class.getResource("/").getPath();
this.keyFilename=keyFilename;
this.path=path;
//System.out.println(path);
/* 读取密匙 */
//System.err.println("***DecryptClassLoader: reading key***");
init();
}
public void init() throws Exception{
//System.out.println(path);
byte rawKey[] = FileUtil.resourceReadToByteArray(path+keyFilename);
DESKeySpec dks = new DESKeySpec(rawKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(dks);
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
//System.err.println("***DecryptClassLoader: creating cipher***");
cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, sr);
}
/**
* 获得加密的class
* @param javaClassName class名,并且要和路径一致
* @return
* @throws Exception
*/
public Class decodeClass(String javaClassName) throws Exception{
/* 创建应用主类的一个实例,通过ClassLoader装入它 */
Class clasz = this.loadClass(javaClassName, false);
return clasz;
}
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
{
if(name.indexOf(".")>-1){
return DecryptClassLoader.class.getClassLoader().loadClass(name);
}
try
{
/* 要创建的Class对象 */
Class clasz = null;
/* 如果类已经在系统缓冲之中,不必再次装入它 */
clasz = findLoadedClass(name);
if (clasz != null)
return clasz;
try
{
/* 读取经过加密的类文件 */
byte classData[] = FileUtil
.resourceReadToByteArray(path+name + ".lib");
if (classData != null)
{
/* 解密 */
byte decryptedClassData[] = cipher.doFinal(classData);
//System.out.println(decryptedClassData.length);
// FileUtil.byteArrayWriteToFile("dd.class", decryptedClassData);
/* 再把它转换成一个类 */
clasz = defineClass(name, decryptedClassData, 0,
decryptedClassData.length);
}
}
catch (IOException fnfe)
{
//fnfe.printStackTrace();
}
/* 如果上面没有成功,尝试用默认的ClassLoader装入它 */
if (clasz == null)
clasz = findSystemClass(name);
/* 如有必要,则装入相关的类 */
if (resolve && clasz != null)
resolveClass(clasz);
return clasz;
}
catch (Exception gse)
{
throw new ClassNotFoundException(gse.toString());
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
5. 在项目启动时的初始化方法里加入
DecryptClassLoader dcl= new DecryptClassLoader("key");
Class clasz =dcl.decodeClass("CAU");
//可以把CAU字符串也做个转换,比如简单的base64下,这样全局搜索这个字符串不会找到,增加点破解难度
clasz.newInstance();
6. 项目的其他代码里调用该加密类的方法
DemoFactory.getRunInter().runMethod("demoStr");
7. 其他说明
项目中DecryptClassLoader类没有被加密,破解者可以修改启动将解密后的类文件导出来。可以把DecryptClassLoader也当成加密类进行多层加密。当然加密代码要在程序中运行,肯定是有办法解密的,只是增加解密破解的难度和时间。