【Java程式碼保護探索之路系列:程式碼加密】之一:程式碼加密開篇
阿新 • • 發佈:2019-01-08
程式碼加密也是對Java程式碼進行保護的一種重要方式,作為Java程式碼加密開篇的文章,本文先舉例介紹,如何利用加密演算法實現對.class檔案進行加密。注意為說明基本原理,本文程式採用命令列進行操作,後續會給出具有UI介面的Java類加密軟體。
一 編寫Java程式碼
1.1 生成安全金鑰
生成金鑰的步驟
- 建立Cipher物件(Java密碼擴充套件包含了Cipher類,這個類是所有加密演算法的超類。)。
- 設定模式和金鑰,生成金鑰的步驟如下所示:
- 為加密演算法獲取KeyGenerator。
- 用隨機源來初始化金鑰發生器,如果密碼塊長度是可變的,還需要制定期望的密碼塊長度。
- 呼叫generateKey方法。
程式碼如下所示:
首先寫個工具類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陣列
*
* @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陣列寫入到檔案
*
* @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();
/* 將金鑰資料儲存到檔案 */
FileUtil.byteArrayWriteToFile(keyFileName, secretKey.getEncoded());
}
}
1.2 加密類檔案
獲取到金鑰後就可以進行類的加密了,原始碼如下所示:
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);
/* 建立用於實際加密的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 + " ***");
}
}
}
1.3 載入加密後的類檔案
因為類經過加密處理,所以要重寫設計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;
/**
* 建構函式:設定解密所需要的物件
*
* @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 讀入密匙,建立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];
/* 傳遞給應用本身的引數 */
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);
/* 建立解密的ClassLoader */
DecryptClassLoader dcl = new DecryptClassLoader(key);
/* 建立應用主類的一個例項,通過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);
/* 建立一個包含main()方法引數的陣列 */
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
{
/* 要建立的Class物件 */
Class clasz = null;
/* 如果類已經在系統緩衝之中,不必再次裝入它 */
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());
}
}
}
二 進行加密和解密
2.1 進入工程的bin目錄
cd D:\workplace_eclipse\CodeEncryption\bin
2.2 執行GenerateKey,生成key。
java com.allenwells.codeencryption.GenerateKey key
這時候在工程目錄下已經生成了key,如下圖所示:
2.3 執行EncryptClasses,加密類檔案
包需要加密的class檔案放入bin目錄下,執行一下命令
java com.allenwells.codeencryption.EncryptClass key TestClass.class
執行完成後,此時的TestClass.class就是已經經過加密的類檔案了,現在用jd-jui來驗證一下.
首先放入未加密的類檔案,如下圖所示:
再放入加密後的類檔案,如下圖所示:
上面兩張圖表明已經加密成功,下面就來載入並執行加密後的類檔案。
我們再來比較下加密前後class位元組碼的變化。
未加密的位元組碼:可以看到第一行前四組十六進位制數為CA FE BA BE,這四個段標識了該檔案為一個可以被Java虛擬機器所識別的class檔案。
加密後的位元組碼:
2.4 載入並執行加密後的類檔案
接下來就是用自定義的ClassLoader載入並執行加密後的類檔案,命令如下所示:
java com.allenwells.codeencryption.DecryptClassLoader key TestClass
執行完畢後,會提示“TestClass run successfully”,即加密類載入並執行成功,全部的操作盒命令如下圖所示: