zoukankan      html  css  js  c++  java
  • Java代码加密与反编译(二):用加密算法DES修改classLoader实现对.class文件加密

    二、利用加密算法DES实现java代码加密

            传统的C/C++自动带有保护机制,但java不同,只要使用反编译工具,代码很容易被暴露,这里需要了解的就是Java的ClassLoader对象

           Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。可以通过定制ClassLoader,在类文件执行之前修改它。在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码

        创建定制ClassLoader对象:只需先获得原始数据,接着就可以进行包含解密在内的任何转换。这里我们可以自己实现loadClass。

     

    制定类装入器

    每一个运行着的JVM已经拥有一个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地文件系统中寻找合适的字节码文件。

    首先创建一个定制ClassLoader类的实例,然后显式地要求它装入另外一个类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。

     

    step1:生成一个安全秘钥。

    在加密或解密任何数据之前需要有一个密匙。密匙是随同被加密的应用一起发布的一小段数据。生成过后的秘钥为key.data。

    Util.java:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
    1. import java.io.*;  
    2.   
    3. public class Util  
    4. {  
    5.   // 把文件读入byte数组  
    6.   static public byte[] readFile(String filename) throws IOException {  
    7.     File file = new File(filename);  
    8.     long len = file.length();  
    9.     byte data[] = new byte[(int)len];  
    10.     FileInputStream fin = new FileInputStream(file);  
    11.     int r = fin.read(data);  
    12.     if (r != len)  
    13.       throw new IOException("Only read "+r+" of "+len+" for "+file);  
    14.     fin.close();  
    15.     return data;  
    16.   }  
    17.   
    18.   // 把byte数组写出到文件  
    19.   static public void writeFile(String filename, byte data[]) throws IOException {  
    20.     FileOutputStream fout = new FileOutputStream(filename);  
    21.     fout.write(data);  
    22.     fout.close();  
    23.   }  
    24. }  

    GenerateKey.java:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
    1. import java.security.SecureRandom;  
    2. import javax.crypto.KeyGenerator;  
    3. import javax.crypto.SecretKey;  
    4.   
    5. public class GenerateKey  
    6. {  
    7.   static public void main(String args[]) throws Exception {  
    8.     String keyFilename = args[0];  
    9.     String algorithm = "DES";  
    10.   
    11.     // 生成密匙  
    12.     SecureRandom sr = new SecureRandom();  
    13.     KeyGenerator kg = KeyGenerator.getInstance(algorithm);  
    14.     kg.init(sr);  
    15.     SecretKey key = kg.generateKey();  
    16.   
    17.     // 把密匙数据保存到文件  
    18.     Util.writeFile(keyFilename, key.getEncoded());  
    19.   }  
    20. }  

    step2:加密待加密的.class文件。

    得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序:

    EncryptClasses.java:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
    1. import java.security.*;  
    2. import javax.crypto.*;  
    3. import javax.crypto.spec.*;  
    4.   
    5. public class EncryptClasses  
    6. {  
    7.   static public void main(String args[]) throws Exception {  
    8.     String keyFilename = args[0];  
    9.     String algorithm = "DES";  
    10.   
    11.     // 生成密匙  
    12.     SecureRandom sr = new SecureRandom();  
    13.     byte rawKey[] = Util.readFile(keyFilename);  
    14.     DESKeySpec dks = new DESKeySpec(rawKey);  
    15.     SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( algorithm );  
    16.     SecretKey key = keyFactory.generateSecret(dks);  
    17.   
    18.     // 创建用于实际加密操作的Cipher对象  
    19.     Cipher ecipher = Cipher.getInstance(algorithm);  
    20.     ecipher.init(Cipher.ENCRYPT_MODE, key, sr);  
    21.   
    22.     // 加密命令行中指定的每一个类  
    23.     for (int i=1; i<args.length; ++i) {  
    24.       String filename = args[i];  
    25.       byte classData[] = Util.readFile(filename);  //读入类文件  
    26.       byte encryptedClassData[] = ecipher.doFinal(classData);  //加密  
    27.       Util.writeFile(filename, encryptedClassData);  // 保存加密后的内容  
    28.       System.out.println("Encrypted "+filename);  
    29.     }  
    30.   }  
    31. }  

    step3:加密待加密的.class文件。

    编译之后用命令行启动程序,下面是源码:

    DecryptStart.java:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
    1. import java.io.*;  
    2. import java.security.*;  
    3. import java.lang.reflect.*;  
    4. import javax.crypto.*;  
    5. import javax.crypto.spec.*;  
    6.   
    7. public class DecryptStart extends ClassLoader  
    8. {  
    9.   // 这些对象在构造函数中设置,以后loadClass()方法将利用它们解密类  
    10.   private SecretKey key;  
    11.   private Cipher cipher;  
    12.   
    13.   // 构造函数:设置解密所需要的对象  
    14.   public DecryptStart(SecretKey key) throws GeneralSecurityException, IOException {  
    15.     this.key = key;  
    16.   
    17.     String algorithm = "DES";  
    18.     SecureRandom sr = new SecureRandom();  
    19.     System.err.println("[DecryptStart: creating cipher]");  
    20.     cipher = Cipher.getInstance(algorithm);  
    21.     cipher.init(Cipher.DECRYPT_MODE, key, sr);  
    22.   }  
    23.   
    24.   // main过程:在这里读入密匙,创建DecryptStart的实例,它就是定制ClassLoader。  
    25.   // 设置好ClassLoader以后,用它装入应用实例,  
    26.   // 最后,通过Java Reflection API调用应用实例的main方法  
    27.   public static void main(String args[]) throws Exception {  
    28.     String keyFilename = args[0];  
    29.     String appName = args[1];  
    30.   
    31.     // 传递给应用本身的参数  
    32.     String realArgs[] = new String[args.length-2];  
    33.     System.arraycopy( args, 2, realArgs, 0, args.length-2 );  
    34.   
    35.     // 读取密匙  
    36.     System.err.println( "[DecryptStart: reading key]" );  
    37.     byte rawKey[] = Util.readFile(keyFilename);  
    38.     DESKeySpec dks = new DESKeySpec(rawKey);  
    39.     SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");  
    40.     SecretKey key = keyFactory.generateSecret(dks);  
    41.   
    42.     // 创建解密的ClassLoader  
    43.     DecryptStart dr = new DecryptStart(key);  
    44.   
    45.     // 创建应用主类的一个实例,通过ClassLoader装入它  
    46.     System.err.println("[DecryptStart: loading "+appName+"]");  
    47.     Class clasz = dr.loadClass(appName);  
    48.   
    49.     // 最后通过Reflection API调用应用实例  
    50.     // 的main()方法  
    51.   
    52.     // 获取一个对main()的引用  
    53.     String proto[] = new String[1];  
    54.     Class mainArgs[] = { (new String[1]).getClass() };  
    55.     Method main = clasz.getMethod("main", mainArgs);  
    56.   
    57.     // 创建一个包含main()方法参数的数组  
    58.     Object argsArray[] = { realArgs };  
    59.     System.err.println("[DecryptStart: running "+appName+".main()]");  
    60.   
    61.     // 调用main()  
    62.     main.invoke(null, argsArray);  
    63.   }  
    64.   
    65.   public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {  
    66.     try {  
    67.       // 要创建的Class对象  
    68.       Class clasz = null;  
    69.   
    70.       // 必需的步骤1:如果类已经在系统缓冲之中,不必再次装入它  
    71.       clasz = findLoadedClass(name);  
    72.   
    73.       if (clasz != null)  
    74.           return clasz;  
    75.   
    76.       // 下面是定制部分  
    77.       try{  
    78.           //读取经过加密的类文件  
    79.           byte classData[] = Util.readFile(name+".class");  
    80.           if(classData != null){  
    81.             byte decryptedClassData[] = cipher.doFinal(classData);  //解密  
    82.               clasz = defineClass( name, decryptedClassData, 0, decryptedClassData.length); // 再把它转换成一个类  
    83.               System.err.println( "[DecryptStart: decrypting class "+name+"]");  
    84.          }                  
    85.       }catch(FileNotFoundException fnfe){  
    86.             
    87.       }  
    88.   
    89.       // 必需的步骤2:如果上面没有成功  
    90.       // 尝试用默认的ClassLoader装入它  
    91.       if (clasz == null)  
    92.           clasz = findSystemClass(name);          
    93.   
    94.       // 必需的步骤3:如有必要,则装入相关的类  
    95.       if (resolve && clasz != null)  
    96.           resolveClass(clasz);          
    97.         
    98.       return clasz;//把类返回给调用者  
    99.         
    100.     } catch(IOException ie) {  
    101.           throw new ClassNotFoundException(ie.toString());  
    102.     } catch(GeneralSecurityException gse) {  
    103.           throw new ClassNotFoundException( gse.toString());  
    104.     }  
    105.   }  
    106. }  

    step4:运行加密程序。

    1.新建java项目,把上面三个.java程序和需要加密的程序.java都放在同一个src目录下,然后在eclipse里构建项目,把所有的.java文件在bin目录下生成.class文件。这里我需要加密jar包里有三个程序:SplitAddress.java、IPSeeker.java、IPEntity.java。


     

    2.然后在命令行里,cd到bin目录下启动程序:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
    1. java com.javacode.GenerateKeykey.data  

    这样就在bin下生成了key.data文件。

     

    3.把bincomjavacode下的待加密的SplitAddress.class、IPSeeker.class、IPEntity.class拷贝到bin目录下。然后bin目录下命令行启动:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
    1. java com.javacode.EncryptClasseskey.data SplitAddress.class IPSeeker.class IPEntity.class  

    该命令把每个.class文件替换成其各自的加密版本。

    (注意:加密版本为bin目录下的.class文件,未加密版本为bincomjavacode下的.class文件)

     

    4.运行经过加密的应用。

     

    对于未经加密的应用(cd到bincomjavacode),正常执行方式如下:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
    1. java IPSeeker arg0 arg1 arg2  

    对于经过加密的应用(cd到bin),则相应的运行方式为:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
    1. java DecryptStart key.data IPSeeker arg0 arg1 arg2  

    step5:验证

    (1) 未加密的可以直接用反编译工具jd-gui查看到源码:



    (2) 经过加密的用反编译工具无法查看:



    step6:一些说明

    虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。解决方法是对启动程序进行高质量模糊处理。或者可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

    另外大多数JVM本身并不安全,还可以修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过上述加密所做的一切工作。

    不过所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。

  • 相关阅读:
    CDH 2、Cloudera Manager的安装
    204 01 Android 零基础入门 03 Java常用工具类 04 Java集合 04 Map集合 01 Map概述
    203 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 07 删除宠物猫信息数据(引入泛型知识点)
    202 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 06 查找宠物猫信息数据
    201 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 05 添加重复的宠物猫信息数据
    200 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 04 添加和显式宠物猫信息
    199 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 03 宠物猫信息管理概述
    198 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 02 案例:在集合中插入字符串
    197 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 01 Set概述
    196 01 Android 零基础入门 03 Java常用工具类 04 Java集合 02 List集合 05 案例:公告的删除和修改
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318061.html
Copyright © 2011-2022 走看看