熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

如何有效防止Java程序源碼被人偷窺?

2013-11-23 18:47:58  來源: Java核心技術 
       Java程序的源代碼很容易被別人偷看只要有一個反編譯器任何人都可以分析別人的代碼本文討論如何在不修改原有程序的情況下通過加密技術保護源代碼

  為什麼要加密?

  對於傳統的C或C++之類的語言來說要在Web上保護源代碼是很容易的只要不發布它就可以遺憾的是Java程序的源代碼很容易被別人偷看只要有一個反編譯器任何人都可以分析別人的代碼Java的靈活性使得源代碼很容易被竊取但與此同時它也使通過加密保護代碼變得相對容易我們唯一需要了解的就是Java的ClassLoader對象當然在加密過程中有關Java Cryptography Extension(JCE)的知識也是必不可少的

  有幾種技術可以模糊Java類文件使得反編譯器處理類文件的效果大打折扣然而修改反編譯器使之能夠處理這些經過模糊處理的類文件並不是什麼難事所以不能簡單地依賴模糊技術來保證源代碼的安全

  我們可以用流行的加密工具加密應用比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)這時最終用戶在運行應用之前必須先進行解密但解密之後最終用戶就有了一份不加密的類文件這和事先不進行加密沒有什麼差別

  Java運行時裝入字節碼的機制隱含地意味著可以對字節碼進行修改JVM每次裝入類文件時都需要一個稱為ClassLoader的對象這個對象負責把新的類裝入正在運行的JVMJVM給ClassLoader一個包含了待裝入類(比如javalangObject)名字的字符串然後由ClassLoader負責找到類文件裝入原始數據並把它轉換成一個Class對象

  我們可以通過定制ClassLoader在類文件執行之前修改它這種技術的應用非常廣泛??在這裡它的用途是在類文件裝入之時進行解密因此可以看成是一種即時解密器由於解密後的字節碼文件永遠不會保存到文件系統所以竊密者很難得到解密後的代碼

  由於把原始字節碼轉換成Class對象的過程完全由系統負責所以創建定制ClassLoader對象其實並不困難只需先獲得原始數據接著就可以進行包含解密在內的任何轉換

  Java 在一定程度上簡化了定制ClassLoader的構建在Java loadClass的缺省實現仍舊負責處理所有必需的步驟但為了顧及各種定制的類裝入過程它還調用一個新的findClass方法

  這為我們編寫定制的ClassLoader提供了一條捷徑減少了麻煩只需覆蓋findClass而不是覆蓋loadClass這種方法避免了重復所有裝入器必需執行的公共步驟因為這一切由loadClass負責

  不過本文的定制ClassLoader並不使用這種方法原因很簡單如果由默認的ClassLoader先尋找經過加密的類文件它可以找到;但由於類文件已經加密所以它不會認可這個類文件裝入過程將失敗因此我們必須自己實現loadClass稍微增加了一些工作量

  定制類裝入器

  每一個運行著的JVM已經擁有一個ClassLoader這個默認的ClassLoader根據CLASSPATH環境變量的值在本地文件系統中尋找合適的字節碼文件

  應用定制ClassLoader要求對這個過程有較為深入的認識我們首先必須創建一個定制ClassLoader類的實例然後顯式地要求它裝入另外一個類這就強制JVM把該類以及所有它所需要的類關聯到定制的ClassLoaderListing 顯示了如何用定制ClassLoader裝入類文件

  【Listing 利用定制的ClassLoader裝入類文件】

  以下是引用片段

     // 首先創建一個ClassLoader對象
  ClassLoader myClassLoader = new myClassLoader();
  // 利用定制ClassLoader對象裝入類文件
  // 並把它轉換成Class對象
  Class myClass = myClassLoaderloadClass( mypackageMyClass );
  // 最後創建該類的一個實例
  Object newInstance = myClassnewInstance();
  // 注意MyClass所需要的所有其他類都將通過
  // 定制的ClassLoader自動裝入 

  如前所述定制ClassLoader只需先獲取類文件的數據然後把字節碼傳遞給運行時系統由後者完成余下的任務

  ClassLoader有幾個重要的方法創建定制的ClassLoader時我們只需覆蓋其中的一個即loadClass提供獲取原始類文件數據的代碼這個方法有兩個參數類的名字以及一個表示JVM是否要求解析類名字的標記(即是否同時裝入有依賴關系的類)如果這個標記是true我們只需在返回JVM之前調用resolveClass

  【Listing ClassLoaderloadClass()的一個簡單實現】

  以下是引用片段

   public Class loadClass( String name boolean resolve )
  throws ClassNotFoundException {
  try {
  // 我們要創建的Class對象
  Class clasz = null;
  // 必需的步驟如果類已經在系統緩沖之中
  // 我們不必再次裝入它
  clasz = findLoadedClass( name );
  if (clasz != null)
  return clasz;
  // 下面是定制部分
  byte classData[] = /* 通過某種方法獲取字節碼數據 */;
  if (classData != null) {
  // 成功讀取字節碼數據現在把它轉換成一個Class對象
  clasz = defineClass( name classData classDatalength );
  }
  // 必需的步驟如果上面沒有成功
  // 我們嘗試用默認的ClassLoader裝入它
  if (clasz == null)
  clasz = findSystemClass( name );
  // 必需的步驟如有必要則裝入相關的類
  if (resolve && clasz != null)
  resolveClass( clasz );
  // 把類返回給調用者
  return clasz;
  } catch( IOException ie ) {
  throw new ClassNotFoundException( ietoString() );
  } catch( GeneralSecurityException gse ) {
  throw new ClassNotFoundException( gsetoString() );
  }
  } 

  Listing 顯示了一個簡單的loadClass實現代碼中的大部分對所有ClassLoader對象來說都一樣但有一小部分(已通過注釋標記)是特有的在處理過程中ClassLoader對象要用到其他幾個輔助方法

  findLoadedClass用來進行檢查以便確認被請求的類當前還不存在loadClass方法應該首先調用它

  defineClass獲得原始類文件字節碼數據之後調用defineClass把它轉換成一個Class對象任何loadClass實現都必須調用這個方法

  findSystemClass提供默認ClassLoader的支持如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法)則可以調用該方法嘗試默認的裝入方式這是很有用的特別是從普通的JAR文件裝入標准Java類時

  resolveClass當JVM想要裝入的不僅包括指定的類而且還包括該類引用的所有其他類時它會把loadClass的resolve參數設置成true這時我們必須在返回剛剛裝入的Class對象給調用者之前調用resolveClass

  加密解密

  Java加密擴展即Java Cryptography Extension簡稱JCE它是Sun的加密服務軟件包含了加密和密匙生成功能JCE是JCA(Java Cryptography Architecture)的一種擴展

  JCE沒有規定具體的加密算法但提供了一個框架加密算法的具體實現可以作為服務提供者加入除了JCE框架之外JCE軟件包還包含了SunJCE服務提供者其中包括許多有用的加密算法比如DES(Data Encryption Standard)和Blowfish

  為簡單計在本文中我們將用DES算法加密和解密字節碼下面是用JCE加密和解密數據必須遵循的基本步驟

  步驟生成一個安全密匙在加密或解密任何數據之前需要有一個密匙密匙是隨同被加密的應用一起發布的一小段數據Listing 顯示了如何生成一個密匙 【Listing 生成一個密匙】

  以下是引用片段

   // DES算法要求有一個可信任的隨機數源
  SecureRandom sr = new SecureRandom();
  // 為我們選擇的DES算法生成一個KeyGenerator對象
  KeyGenerator kg = KeyGeneratorgetInstance( DES );
  kginit( sr );
  // 生成密匙
  SecretKey key = kggenerateKey();
  // 獲取密匙數據
  byte rawKeyData[] = keygetEncoded();
  /* 接下來就可以用密匙進行加密或解密或者把它保存
  為文件供以後使用 */
  doSomething( rawKeyData );  
步驟加密數據得到密匙之後接下來就可以用它加密數據除了解密的ClassLoader之外一般還要有一個加密待發布應用的獨立程序(見Listing ) 【Listing 用密匙加密原始數據】

  以下是引用片段

   // DES算法要求有一個可信任的隨機數源
  SecureRandom sr = new SecureRandom();
  byte rawKeyData[] = /* 用某種方法獲得密匙數據 */;
  // 從原始密匙數據創建DESKeySpec對象
  DESKeySpec dks = new DESKeySpec( rawKeyData );
  // 創建一個密匙工廠然後用它把DESKeySpec轉換成
  // 一個SecretKey對象
  SecretKeyFactory keyFactory = SecretKeyFactorygetInstance( DES );
  SecretKey key = keyFactorygenerateSecret( dks );
  // Cipher對象實際完成加密操作
  Cipher cipher = CiphergetInstance( DES );
  // 用密匙初始化Cipher對象
  cipherinit( CipherENCRYPT_MODE key sr );
  // 現在獲取數據並加密
  byte data[] = /* 用某種方法獲取數據 */
  // 正式執行加密操作
  byte encryptedData[] = cipherdoFinal( data );
  // 進一步處理加密後的數據
  doSomething( encryptedData );  
  步驟解密數據運行經過加密的應用時ClassLoader分析並解密類文件操作步驟如Listing 所示 【Listing 用密匙解密數據】

     // DES算法要求有一個可信任的隨機數源
  SecureRandom sr = new SecureRandom();
  byte rawKeyData[] = /* 用某種方法獲取原始密匙數據 */;
  // 從原始密匙數據創建一個DESKeySpec對象
  DESKeySpec dks = new DESKeySpec( rawKeyData );
  // 創建一個密匙工廠然後用它把DESKeySpec對象轉換成
  // 一個SecretKey對象
  SecretKeyFactory keyFactory = SecretKeyFactorygetInstance( DES );
  SecretKey key = keyFactorygenerateSecret( dks );
  // Cipher對象實際完成解密操作
  Cipher cipher = CiphergetInstance( DES );
  // 用密匙初始化Cipher對象
  cipherinit( CipherDECRYPT_MODE key sr );
  // 現在獲取數據並解密
  byte encryptedData[] = /* 獲得經過加密的數據 */
  // 正式執行解密操作
  byte decryptedData[] = cipherdoFinal( encryptedData );
  // 進一步處理解密後的數據
  doSomething( decryptedData );  

  應用實例

  前面介紹了如何加密和解密數據要部署一個經過加密的應用步驟如下

  步驟創建應用我們的例子包含一個App主類兩個輔助類(分別稱為Foo和Bar)這個應用沒有什麼實際功用但只要我們能夠加密這個應用加密其他應用也就不在話下

  步驟生成一個安全密匙在命令行利用GenerateKey工具(參見GenerateKeyjava)把密匙寫入一個文件 % java GenerateKey keydata

  步驟加密應用在命令行利用EncryptClasses工具(參見EncryptClassesjava)加密應用的類 % java EncryptClasses keydata Appclass Fooclass Barclass

  該命令把每一個class文件替換成它們各自的加密版本

  步驟運行經過加密的應用用戶通過一個DecryptStart程序運行經過加密的應用DecryptStart程序如Listing 所示 【Listing DecryptStartjava啟動被加密應用的程序】

  以下是引用片段

     import javaio*;
  import javasecurity*;
  import javalangreflect*;
  import javaxcrypto*;
  import javaxcryptospec*;
  public class DecryptStart extends ClassLoader
  {
  // 這些對象在構造函數中設置
  // 以後loadClass()方法將利用它們解密類
  private SecretKey key;
  private Cipher cipher;
  // 構造函數設置解密所需要的對象
  public DecryptStart( SecretKey key ) throws GeneralSecurityException
  IOException {
  thiskey = key;
  String algorithm = DES;
  SecureRandom sr = new SecureRandom();
  Systemerrprintln( [DecryptStart: creating cipher] );
  cipher = CiphergetInstance( algorithm );
  cipherinit( CipherDECRYPT_MODE key sr );
  }
  // main過程我們要在這裡讀入密匙創建DecryptStart的
  // 實例它就是我們的定制ClassLoader
  // 設置好ClassLoader以後我們用它裝入應用實例
  // 最後我們通過Java Reflection API調用應用實例的main方法
  static public void main( String args[] ) throws Exception {
  String keyFilename = args[];
  String appName = args[];
  // 這些是傳遞給應用本身的參數
  String realArgs[] = new String[argslength];
  Systemarraycopy( args realArgs argslength );
  // 讀取密匙
  Systemerrprintln( [DecryptStart: reading key] );
  byte rawKey[] = UtilreadFile( keyFilename );
  DESKeySpec dks = new DESKeySpec( rawKey );
  SecretKeyFactory keyFactory = SecretKeyFactorygetInstance( DES );
  SecretKey key = keyFactorygenerateSecret( dks );
  // 創建解密的ClassLoader
  DecryptStart dr = new DecryptStart( key );
  // 創建應用主類的一個實例
  // 通過ClassLoader裝入它
  Systemerrprintln( [DecryptStart: loading +appName+] );
  Class clasz = drloadClass( appName );
  // 最後通過Reflection API調用應用實例
  // 的main()方法
  // 獲取一個對main()的引用
  String proto[] = new String[];
  Class mainArgs[] = { (new String[])getClass() };
  Method main = claszgetMethod( main mainArgs );
  // 創建一個包含main()方法參數的數組
  Object argsArray[] = { realArgs };
  Systemerrprintln( [DecryptStart: running +appName+main()] );
  // 調用main()
  maininvoke( 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[] = UtilreadFile( name+class );
  if (classData != null) {
  // 解密
  byte decryptedClassData[] = cipherdoFinal( classData );
  // 再把它轉換成一個類
  clasz = defineClass( name decryptedClassData
   decryptedClassDatalength );
  Systemerrprintln( [DecryptStart: 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( ietoString()
  );
  } catch( GeneralSecurityException gse ) {
  throw new ClassNotFoundException( gsetoString()
  );
  }
  }
  } 
 對於未經加密的應用正常執行方式如下 % java App arg arg arg

  對於經過加密的應用則相應的運行方式為 % java DecryptStart keydata App arg arg arg

  DecryptStart有兩個目的一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時DecryptStart還包含一個main過程它創建解密器實例並用它裝入和運行應用示例應用App的代碼包含在AppjavaFoojava和Barjava內Utiljava是一個文件I/O工具本文示例多處用到了它完整的代碼請從本文最後下載

  注意事項

  我們看到要在不修改源代碼的情況下加密一個Java應用是很容易的不過世上沒有完全安全的系統本文的加密方式提供了一定程度的源代碼保護但對某些攻擊來說它是脆弱的

  雖然應用本身經過了加密但啟動程序DecryptStart沒有加密攻擊者可以反編譯啟動程序並修改它把解密後的類文件保存到磁盤降低這種風險的辦法之一是對啟動程序進行高質量的模糊處理或者啟動程序也可以采用直接編譯成機器語言的代碼使得啟動程序具有傳統執行文件格式的安全性

  另外還要記住的是大多數JVM本身並不安全狡猾的黑客可能會修改JVM從ClassLoader之外獲取解密後的代碼並保存到磁盤從而繞過本文的加密技術Java沒有為此提供真正有效的補救措施

  不過應該指出的是所有這些可能的攻擊都有一個前提這就是攻擊者可以得到密匙如果沒有密匙應用的安全性就完全取決於加密算法的安全性雖然這種保護代碼的方法稱不上十全十美但它仍不失為一種保護知識產權和敏感用戶數據的有效方案


From:http://tw.wingwit.com/Article/program/Java/hx/201311/25751.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.