1. 程式人生 > >javaee加密部署專案通過tomcat使用自定義的classload解密

javaee加密部署專案通過tomcat使用自定義的classload解密

如上述文章所說,繼承tomcat的WebappClassLoader自定義自己的classload,並不適用於spring。

原因是spring載入類是用另外的方式來載入。

意思應該是是需要封裝的jar程式碼中不能有spring的註解(依賴注入、控制器宣告等等)

原理:

通過加密java程式碼的class檔案,實現對原始碼的保護,並通過自定義的classload檔案來解密class檔案,把類載入到專案中

自定義自己的classload,並重寫findClass方法。類載入器通過呼叫findClass方法,尋找並載入類檔案。

通過重寫findClass方法,可以把預先加密好的class檔案,解密後再載入到專案中

package generator;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.apache.catalina.loader.WebappClassLoader;

/**
 * 自定義的classloader 可以解密檔案並載入
 * 
 * @author uikoo9
 */
public class UClassLoader extends WebappClassLoader {
	
	/**
	 * 演算法
	 */
	private String algorithmStr="AES/ECB/PKCS5Padding";
	
	/**
	 * key
	 */
	private String keyStr="abcdefg123456789";
	
	/**
	 * 預設構造器
	 * 
	 * @throws Exception
	 */
	public UClassLoader() {
		super();
	}

	/**
	 * 預設構造器
	 * 
	 * @param parent
	 * @throws Exception
	 */
	public UClassLoader(ClassLoader parent) {
		super(parent);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String)
	 */
	public Class findClass(String name) throws ClassNotFoundException {
		if (name.contains("com.A")
				|| name.contains("com.B")
				|| name.contains("com.C")) {
			return findClassEncrypt(name);
		} else {
			return super.findClass(name);
		}
	}

	/**
	 * 查詢class
	 * 
	 * @param name
	 * @return
	 * @throws ClassNotFoundException
	 */
	private Class findClassEncrypt(String name) throws ClassNotFoundException {
		byte[] classBytes = null;
		try {
			classBytes = loadClassBytesEncrypt(name);
		} catch (Exception e) {
			e.printStackTrace();
		}

		Class cl = defineClass(name, classBytes, 0, classBytes.length);
		if (cl == null) {
			System.out.println("找不到該類:" + name);
			throw new ClassNotFoundException(name);
		}
		return cl;
	}

	/**
	 * 載入加密後的class位元組流
	 * 
	 * @param name
	 * @return
	 * @throws Exception
	 */
	private byte[] loadClassBytesEncrypt(String name) throws Exception {
		// 獲取當前檔案路徑
		// File directory = new File("");
		// String parentPath = directory.getAbsolutePath()+File.separator;
		String parentPath = "F:\\";
		List<String> basepath = new ArrayList<String>();
		basepath.add(parentPath + "A\\");// 專案實體地址
		basepath.add(parentPath + "B\\");// 專案實體地址
		basepath.add(parentPath + "C\\");// 專案實體地址
		String cname = null;
		File file = null;
		for (String path : basepath) {
			cname = path + name.replace('.', '\\') + ".cls";
			file = new File(cname);
			if (file.exists()) {
				break;
			}
		}

		if (file != null) {
			if (!file.exists()) {
				throw new Exception("File Not found:" + cname);
			}
		}
		FileInputStream in = new FileInputStream(cname);
		try {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			byte[] buf = new byte[1024 * 100];// 100KB
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				buffer.write(buf, 0, len);
			}
			in.close();
			return decrypt(parseHexStr2Byte(new String(buffer.toByteArray())));
			
		} finally {
			in.close();
		}
	}

	/**
	 * 解密
	 * 
	 * @param content
	 * @return
	 */
	private byte[] decrypt(byte[] content) {
		try {
			byte[] keyBytes = this.keyStr.getBytes("utf-8");
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			Cipher cipher = Cipher.getInstance(this.algorithmStr);
			cipher.init(Cipher.DECRYPT_MODE, key);
			byte[] result = cipher.doFinal(content);
			return result;
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 十六進位制字串轉位元組
	 * 
	 * @param hexStr
	 * @return
	 */
	private byte[] parseHexStr2Byte(String hexStr) {
		if (hexStr.length() < 1)
			return null;
		byte[] result = new byte[hexStr.length() / 2];
		for (int i = 0; i < hexStr.length() / 2; i++) {
			int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
			int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
			result[i] = (byte) (high * 16 + low);
		}
		return result;
	}


}
在tomcat的content.xml檔案中,配置自定義的classload為tomcat預設的classload

在<Content>中新增如下程式碼

<Loader loaderClass="generator.UClassLoader" delegate="true"></Loader>
指定環境上下文類載入為自定義的classload

注意:自定義的classload放在tomcat的lib下,並且資料夾層級結構需要跟包名一致generator/UClassLoader.class

大概步驟如下

1、對需要加密的class進行演算法加密,本人是用AES加密

2、把加密後的class檔案放到固定的地方,把專案中的對應class刪除

3、修改context.xml的指定上下文類載入器為自定義載入器

4、啟動tomcat

另外:如果不想自定義tomcat的classload,也可以自定義classload繼承URLClassLoader

程式碼方式類似,也是通過重寫findClass方法。編寫好自定義classload後,在專案中例項化該類。

package classload;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class MyClassLoader extends URLClassLoader {

	/**
	 * 演算法
	 */
	private String algorithmStr="AES/ECB/PKCS5Padding";
	
	/**
	 * key
	 */
	private String keyStr="abcdefg123456789";
	
    public MyClassLoader(URL[] urls){
		super(urls);
	}
	
	public MyClassLoader(URL[] urls, ClassLoader parent){
		super(urls,parent);
	}
	


	public static void main(String[] args) throws MalformedURLException {
		URL url = new File("a.jar").toURI().toURL();
		MyClassLoader classLoader = new MyClassLoader(new URL[]{url},Thread.currentThread().getContextClassLoader());
		//設定自定義classload為環境上下文類載入器
		Thread.currentThread().setContextClassLoader(classLoader);
	}
	
	public Class findClass(String name) throws ClassNotFoundException {
		if (name.contains("com.A")
				|| name.contains("com.B")
				|| name.contains("com.C")) {
			return findClassEncrypt(name);
		} else {
			return super.findClass(name);
		}
	}

	/**
	 * 查詢class
	 * 
	 * @param name
	 * @return
	 * @throws ClassNotFoundException
	 */
	private Class findClassEncrypt(String name) throws ClassNotFoundException {
		byte[] classBytes = null;
		try {
			classBytes = loadClassBytesEncrypt(name);
		} catch (Exception e) {
			e.printStackTrace();
		}

		Class cl = defineClass(name, classBytes, 0, classBytes.length);
		if (cl == null) {
			System.out.println("找不到該類:" + name);
			throw new ClassNotFoundException(name);
		}
		return cl;
	}

	/**
	 * 載入加密後的class位元組流
	 * 
	 * @param name
	 * @return
	 * @throws Exception
	 */
	private byte[] loadClassBytesEncrypt(String name) throws Exception {
		// 獲取當前檔案路徑
		// File directory = new File("");
		// String parentPath = directory.getAbsolutePath()+File.separator;
		String parentPath = "F:\\";
		List<String> basepath = new ArrayList<String>();
		basepath.add(parentPath + "A\\");// 專案實體地址
		basepath.add(parentPath + "B\\");// 專案實體地址
		basepath.add(parentPath + "C\\");// 專案實體地址
		String cname = null;
		File file = null;
		for (String path : basepath) {
			cname = path + name.replace('.', '\\') + ".cls";
			file = new File(cname);
			if (file.exists()) {
				break;
			}
		}

		if (file != null) {
			if (!file.exists()) {
				throw new Exception("File Not found:" + cname);
			}
		}
		FileInputStream in = new FileInputStream(cname);
		try {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			byte[] buf = new byte[1024 * 100];// 100KB
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				buffer.write(buf, 0, len);
			}
			in.close();
			return decrypt(parseHexStr2Byte(new String(buffer.toByteArray())));
			
		} finally {
			in.close();
		}
	}

	/**
	 * 解密
	 * 
	 * @param content
	 * @return
	 */
	private byte[] decrypt(byte[] content) {
		try {
			byte[] keyBytes = this.keyStr.getBytes("utf-8");
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			Cipher cipher = Cipher.getInstance(this.algorithmStr);
			cipher.init(Cipher.DECRYPT_MODE, key);
			byte[] result = cipher.doFinal(content);
			return result;
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 十六進位制字串轉位元組
	 * 
	 * @param hexStr
	 * @return
	 */
	private byte[] parseHexStr2Byte(String hexStr) {
		if (hexStr.length() < 1)
			return null;
		byte[] result = new byte[hexStr.length() / 2];
		for (int i = 0; i < hexStr.length() / 2; i++) {
			int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
			int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
			result[i] = (byte) (high * 16 + low);
		}
		return result;
	}
}
專案中載入該類
		URL url = new File("a.jar").toURI().toURL();
		MyClassLoader classLoader = new MyClassLoader(new URL[]{url},Thread.currentThread().getContextClassLoader());
		//設定自定義classload為環境上下文類載入器
		Thread.currentThread().setContextClassLoader(classLoader);

建構函式必須傳URL物件引數進去,所以要隨便指定一個jar。