提到Java代碼加密,常見方式是使用代碼混淆工具,如proguard。混淆是一種邏輯層面的加密,被混淆的代碼仍可以反編譯,但由於命名與程式流程上的等效替換,使得程式的可讀性變的很差,導致代碼難以被理解和盜用。但若有方法使代碼根本無法被反編譯,效果顯然優於邏輯上的加密,而一種可以實現的方式就是位元組碼加密。
Java代碼的實際運行與原始碼(*.java)關係不大,只依賴於編譯後的位元組碼檔案(*.class)。class檔案的內容有非常緊湊和嚴格的約定,使JVM可以識別和執行代碼功能;反編譯工具也是利用這種約定的結構將位元組碼反向解析成源碼。只要破壞class檔案的結構,就能使這個檔案完全失效,變得不可能被反編譯。
當然,這樣加密的class檔案也無法被JVM正常載入,不過java的類載入機制是支援自訂,這就給解密留出了空間。可以用自訂的類載入器實現“先解密,後載入”,使JVM能“正常”執行被加密的class檔案。以下就來實現上述位元組碼加密策略。
首先寫一個加密器介面,方便實現各種位元組碼密碼編譯演算法,兩個介面方法就是將一個位元組數組進行某種變換與逆變換:
以下是一個最簡單的實現:
這個加密器只是將首位元組改為一個自訂的特殊位元組magicIndex,以下是用位元組碼查看工具查看某個java類位元組碼的加密結果:
這麼簡單的“演算法”也能加密?以下是用Eclipse的反編譯外掛程式JD查看加密檔案的結果:
這印證了位元組碼檔案的內容是具有嚴格約束的結構,相差一個位元組就能破壞整個檔案。如使用稍複雜的密碼編譯演算法,反編譯就變的完全不可能了。
接下來寫一個jar包加密方法,邏輯很簡單,就是將class檔案從原包提取出來,加密後寫到新包裡去
類似的,解密方法從一個Jar包中擷取一個(加密後的)class檔案,並解密成JVM可識別的位元組碼。這裡作了一點小變化,把多種加密器寫在一個Map(CipherConfig.cipherMap)裡,用加密檔案的首位元組作為標識:
如果是從檔案系統(而不是Jar包)載入,還要更簡單一點,因為不需要通過ZipEntry迭代器來定位檔案名稱。
下一步是怎樣調用解密方法,這涉及Java的類載入機制。通常自訂類載入都是通過繼承java.lang.ClassLoader類或其子類,重寫findClass(name)方法,再利用反射機制調用方法。但此處我們無從瞭解一個發布了的加密的項目中,各類的載入時機和順序,也不知道類之間的參考關聯性(這些是由JVM的執行機制確定的),因此通過通常的方式難以實現讓一個加密的Jar包在JVM上運行起來。為解決這個問題,這裡採取一種聽起來有點“嚇人”的方法,那就是修改java系統類別源碼。下面先給出實現方法。
自訂的類都是通過sun.misc.Lancher$AppClassLoader.loadClass(name)這個方法來載入。該方法的調用時機由JVM底層決定,傳回值被直接載入到JVM記憶體。其源碼如下:
最後一行本質上就是調用底層原生方法(後續文章還會涉及)將位元組碼載入到記憶體。如果對應類名的位元組碼檔案是加密的,這個方法將無法解析,並拋出ClassNotFoundException異常。修改的方式很簡單,當預設載入失敗後,嘗試用之前自訂的解密方法解密載入:
以上類修改以後,將編譯產生的bin/sun/misc/Launcher$AppClassLoader.class檔案(注意命名空間必須與系統相同)替換掉jre\lib\rt.jar包中的對應檔案,就能正常運行加密後的Jar包了。如將loadFromJar方法寫在自訂的其他類中(如工具類),那麼這個類也要一同添加到rt.jar。並且命名空間也必須為sun.misc,否則系統將無法載入這個類本身。關於重寫系統類別的命名空間問題,後續還會討論。
修改系統類別的“代價”是程式發布時需要捆綁一個自訂的JRE,這看起來讓人“不太舒服”,但仔細分析的話其實利大於弊。第一,增強了程式的獨立性和完整性,不會產生JVM不相容問題,而在沒有安裝Java的終端,附帶一個JVM則是必不可少的;第二,進一步增強加密強度,因為用通用的JRE無法運行加密代碼;第三,修改系統類別甚至JVM源碼有助於瞭解Java虛擬機器底層原理,而自訂系統類別和JVM也使得開發人員對代碼的控制能力大為。唯一的不足可能是捆綁JRE會增加程式的總體積,不過通過一些不算複雜的最佳化措施(可以搜尋“綠色JRE”或“JRE瘦身”等關鍵字來瞭解),大部分程式所必須的JRE都可以壓縮到10MB以內,加上應用後續文章將討論的“輕用戶端模式”,這一點空間上的代價完全是可以接收的。
如果有對輕用戶端感興趣的同學,也可以加入我們的群:291694807。本群主要用於討論輕用戶端,使用技術包括qt、flex、java、vc等。
也可以關注我的微博 http://weibo.com/liuxue9527