基于ClassLoader的java代码加密的经验分享

   原理就是 生成项目时将待加密的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也当成加密类进行多层加密。当然加密代码要在程序中运行,肯定是有办法解密的,只是增加解密破解的难度和时间。

猜你喜欢

转载自my.oschina.net/passerman/blog/1825437