作者:郭嘉
郵箱:allenwells@163.com
博客:http://blog.csdn.net/allenwells
github:https://github.com/AllenWells3
代碼加密也是對Java代碼進行保護的一種重要方式,作為Java代碼加密開篇的文章,本文先舉例介紹,如何利用加密算法實現(xiàn)對.class文件進行加密。注意為說明基本原理,本文程序采用命令行進行操作,后續(xù)會給出具有UI界面的Java類加密軟件。
代碼如下所示:
首先寫個工具類FileUtil.java用于文件讀取和寫入,源碼如下所示:
package com.allenwells.codeencryption.util;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileUtil{ /** * 將文件讀入byte數(shù)組 * * @param fileName * @return * @throws IOException */ static public byte[] fileReadToByteArray(String fileName) throws IOException { File file = new File(fileName); long fileLength = file.length(); byte fileData[] = new byte[(int) fileLength]; FileInputStream fis = new FileInputStream(file); int readLength = fis.read(fileData); if (readLength != fileLength) { System.err.println("***Only read " + readLength + " of " + fileLength + " for file " + file + " ***"); } fis.close(); return fileData; } /** * 將byte數(shù)組寫入到文件 * * @param fileName * @param data * @throws IOException */ static public void byteArrayWriteToFile(String fileName, byte[] data) throws IOException { FileOutputStream fos = new FileOutputStream(fileName); fos.write(data); fos.close(); }}
生成密鑰文件,可由命令行傳入密鑰的名字,這里使用key.data,源碼如下所示:
package com.allenwells.codeencryption;import java.security.SecureRandom;import javax.crypto.SecretKey;import javax.crypto.KeyGenerator;import com.allenwells.codeencryption.util.FileUtil;public class GenerateKey{ static public void main(String args[]) throws Exception { String keyFileName = args[0]; String algorithm = "DES"; /* 生成密鑰 */ SecureRandom secureRandom = new SecureRandom(); KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); keyGenerator.init(secureRandom); SecretKey secretKey = keyGenerator.generateKey(); /* 將密鑰數(shù)據(jù)保存到文件 */ FileUtil.byteArrayWriteToFile(keyFileName, secretKey.getEncoded()); }}
獲取到密鑰后就可以進行類的加密了,源碼如下所示:
package com.allenwells.codeencryption;import java.security.SecureRandom;import javax.crypto.Cipher;import javax.crypto.SecretKeyFactory;import javax.crypto.SecretKey;import javax.crypto.spec.DESKeySpec;import com.allenwells.codeencryption.util.FileUtil;public class EncryptClass{ static public void main(String args[]) throws Exception { String keyFileName = args[0]; String algorithm = "DES"; /* 生成密鑰 */ SecureRandom secureRandom = new SecureRandom(); byte rawKey[] = FileUtil.fileReadToByteArray(keyFileName); DESKeySpec desKeySpec = new DESKeySpec(rawKey); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); /* 創(chuàng)建用于實際加密的Cipher對象 */ Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, secretKey, secureRandom); /* 加密命令行中指定的每一類 */ for (int i = 1; i < args.length; i++) { String fileName = args[i]; /* 讀入類文件 */ byte classData[] = FileUtil.fileReadToByteArray(fileName); /* 加密類文件 */ byte encryptedClassData[] = cipher.doFinal(classData); /* 保存加密后的文件 */ FileUtil.byteArrayWriteToFile(fileName, encryptedClassData); System.out.println("***Encrypted " + fileName + " ***"); } }}
因為類經過加密處理,所以要重寫設計ClassLoader來進行加密類文件的加載,源碼如所示:
package com.allenwells.codeencryption;import java.io.IOException;import java.io.FileNotFoundException;import java.security.SecureRandom;import java.security.GeneralSecurityException;import java.lang.reflect.Method;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;import com.allenwells.codeencryption.util.FileUtil;public class DecryptClassLoader extends ClassLoader{ private SecretKey key; private Cipher cipher; /** * 構造函數(shù):設置解密所需要的對象 * * @param key * @throws GeneralSecurityException * @throws IOException */ public DecryptClassLoader(SecretKey key) throws GeneralSecurityException, IOException { 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); } /** * main過程: 1 讀入密匙,創(chuàng)建DecodeClassLoader的實例,它就是定制ClassLoader。 * 2 設置好ClassLoader以后,用它裝入應用實例, * 3 最后,通過Java Reflection API調用應用實例的main方法 * * @param args * @throws Exception */ public static void main(String args[]) throws Exception { String keyFilename = args[0]; String javaClassName = args[1]; /* 傳遞給應用本身的參數(shù) */ String realArgs[] = new String[args.length - 2]; System.arraycopy(args, 2, realArgs, 0, args.length - 2); /* 讀取密匙 */ System.err.println("***DecryptClassLoader: reading key***"); byte rawKey[] = FileUtil.fileReadToByteArray(keyFilename); DESKeySpec dks = new DESKeySpec(rawKey); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey key = keyFactory.generateSecret(dks); /* 創(chuàng)建解密的ClassLoader */ DecryptClassLoader dcl = new DecryptClassLoader(key); /* 創(chuàng)建應用主類的一個實例,通過ClassLoader裝入它 */ System.err.println("***DecryptClassLoader: loading " + javaClassName + " ***"); Class clasz = dcl.loadClass(javaClassName, false); /* 獲取一個對main()的引用 */ String proto[] = new String[1]; Class mainArgs[] = { (new String[1]).getClass() }; Method main = clasz.getMethod("main", mainArgs); /* 創(chuàng)建一個包含main()方法參數(shù)的數(shù)組 */ Object argsArray[] = { realArgs }; System.err.println("***DecryptClassLoader: running " + javaClassName + ".main()***"); /* 調用main() */ main.invoke(null, argsArray); } public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { try { /* 要創(chuàng)建的Class對象 */ Class clasz = null; /* 如果類已經在系統(tǒng)緩沖之中,不必再次裝入它 */ clasz = findLoadedClass(name); if (clasz != null) return clasz; try { /* 讀取經過加密的類文件 */ byte classData[] = FileUtil .fileReadToByteArray(name + ".class"); if (classData != null) { System.out .println("***DecryptClassLoader: decode begin***"); /* 解密 */ byte decryptedClassData[] = cipher.doFinal(classData); /* 再把它轉換成一個類 */ clasz = defineClass(name, decryptedClassData, 0, decryptedClassData.length); System.err.println("***DecryptClassLoader: decrypting class " + name + " ***"); } } catch (FileNotFoundException fnfe) { } /* 如果上面沒有成功,嘗試用默認的ClassLoader裝入它 */ if (clasz == null) clasz = findSystemClass(name); /* 如有必要,則裝入相關的類 */ if (resolve && clasz != null) resolveClass(clasz); return clasz; } catch (IOException ie) { throw new ClassNotFoundException(ie.toString()); } catch (GeneralSecurityException gse) { throw new ClassNotFoundException(gse.toString()); } }}
cd D:\workplace_eclipse\CodeEncryption\bin
java com.allenwells.codeencryption.GenerateKey key
這時候在工程目錄下已經生成了key,如下圖所示:
包需要加密的class文件放入bin目錄下,運行一下命令
java com.allenwells.codeencryption.EncryptClass key TestClass.class
運行完成后,此時的TestClass.class就是已經經過加密的類文件了,現(xiàn)在用jd-jui來驗證一下.
首先放入未加密的類文件,如下圖所示:
再放入加密后的類文件,如下圖所示:
上面兩張圖表明已經加密成功,下面就來載入并運行加密后的類文件。
我們再來比較下加密前后class字節(jié)碼的變化。
未加密的字節(jié)碼:可以看到第一行前四組十六進制數(shù)為CA FE BA BE,這四個段標識了該文件為一個可以被Java虛擬機所識別的class文件。
加密后的字節(jié)碼:
接下來就是用自定義的ClassLoader加載并運行加密后的類文件,命令如下所示:
java com.allenwells.codeencryption.DecryptClassLoader key TestClass
執(zhí)行完畢后,會提示“TestClass run successfully”,即加密類加載并執(zhí)行成功,全部的操作盒命令如下圖所示: