1. 程式人生 > >【Java程式碼保護探索之路系列:程式碼加密】之一:程式碼加密開篇

【Java程式碼保護探索之路系列:程式碼加密】之一:程式碼加密開篇

程式碼加密也是對Java程式碼進行保護的一種重要方式,作為Java程式碼加密開篇的文章,本文先舉例介紹,如何利用加密演算法實現對.class檔案進行加密。注意為說明基本原理,本文程式採用命令列進行操作,後續會給出具有UI介面的Java類加密軟體。

一 編寫Java程式碼

1.1 生成安全金鑰

生成金鑰的步驟

  1. 建立Cipher物件(Java密碼擴充套件包含了Cipher類,這個類是所有加密演算法的超類。)。
  2. 設定模式和金鑰,生成金鑰的步驟如下所示:
    1. 為加密演算法獲取KeyGenerator。
    2. 用隨機源來初始化金鑰發生器,如果密碼塊長度是可變的,還需要制定期望的密碼塊長度。
    3. 呼叫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”,即加密類載入並執行成功,全部的操作盒命令如下圖所示:

這裡寫圖片描述