在某些情況下,java開發者可能希望保護自己的勞動成果,防止自己編寫的源代碼被競爭對手或者其他組織和個人輕易獲取而危害自己的利益,最簡單有效的辦法就是對編譯後的java類文件進行混淆處理。本文介紹一款這樣的工具yguard。
yGruard是一個功能比較強大的java類文件的混淆工具,特別適合與ant工具集成使用。
本文對yguard的基本元素做一些簡單的介紹,並列舉了一些簡單的ant任務例子,在實際工程項目中可以參考這些樣例。
1. 安裝
在使用yGruard之前,必須首先確保系統中已經有可以正常使用的Ant工具。Ant工具的使用的介紹不屬於本文的範疇。
然後在官方網站http://www.yworks.com/products/yguard下載yguard的工具包,解壓出yguard.jar類庫,yguard當前只有一個jar包。
解壓出之後,把它放置在你的ant工具能夠找到的路徑中。你可以使用絕對路徑,但是在下面的例子中。
爲了在你現有的Ant任務中使用yguard進行混淆工作,你可以採取下邊兩種做法之一。一、你可以在你的build腳本中直接插入下面的代碼片。
<taskdef name="obfuscate" classname="com.yworks.yguard.ObfuscatorTask" classpath="yguard.jar" />
<target name="obfuscate"> <obfuscate> <!--根據你的需求修改 obfuscate 元素屬性 --> </obfuscate> </target>
|
二、你也可以把taskdef元素放置在obfuscate元素中,示例如下:
<target name="obfuscate"> <taskdef name="obfuscate" classname="com.yworks.yguard.ObfuscatorTask" classpath="yguard.jar" /> <obfuscate> <!--根據你的需求修改 obfuscate 元素屬性 --> </obfuscate> </target>
|
2. 創建初始ant任務
我們從經典的helloworld示例入手,逐步學習使用yguard混淆器工具。
2.1. 編寫helloword程序
我們先編寫一個簡單的HelloWorld程序,代碼如下:
package helloworld;
public class HelloWorld { public String publicAttribute = "i am a public attrubute.";
protected String protectedAttribute = "i am a protected attrubute.";
private String privateAttribute = "i am a private attrubute.";
public void publicSayHello() { System.out.println("hello world in public . "); }
protected void protectedSayHello() { System.out.println("hello world in protected . "); }
private void privateSayHello() { System.out.println("hello world in private . "); }
public static void main(String args[]) { HelloWorld hello = new HelloWorld(); hello.publicSayHello(); } }
|
這個HelloWorld程序有不同可見性的屬性,不同可見性的方法。在下面的過程中,我們將逐步演示yguard的強大功能。
2.2. 創建ant任務
創建一個初始的ant腳本任務。
<?xml version="1.0" encoding="UTF-8"?> <project name="project" default="init" basedir="."> <!-- 設置我們Ant任務的初始化工作 --> <target name="init"> <!--工程名稱爲helloworld--> <property name="project_name" value="helloworld" /> <!--java源文件所在路徑是當前路徑的src--> <property name="srcDir" value="." /> <!--編譯生成的class文件在當前路徑的classes目錄下--> <property name="classDir" value="classes" /> <!--jar包名稱爲本工程的名稱加上.jar後綴名--> <property name="jar" value="${project_name}_temp.jar" /> <!--jar包名稱爲本工程的名稱加上.jar後綴名--> <property name="obfjar" value="${project_name}.jar" /> <!--yguard混淆器工作產生的日誌文件--> <property name="obfuscationlog" value="${project_name}_obf_log.xml" /> <mkdir dir="${classDir}" /> </target>
<!-- 編譯 --> <target name="compile" depends="init"> <javac srcdir="${srcDir}" destdir="${classDir}"> </javac> </target>
<!-- 打包 --> <target name="jar" depends="compile"> <jar jarfile="${jar}" basedir="${classDir}" includes="helloworld/**"> </jar> </target>
<!-- 刪除所有已經編譯的class文件 --> <target name="clean" depends="init"> <delete dir="${classDir}" includeEmptyDirs="true" /> </target> </project>
<!-- build.xml 文件結束 --> |
2.3. 執行ant任務
執行這個腳本,可以看到控制檯有如下相似的輸出:
Buildfile: D:/work/lspworkspace/helloworld/build.xml init: init: compile: [javac] Compiling 1 source file to D:/work/lspworkspace/helloworld/classes init: compile: [javac] Compiling 1 source file to D:/work/lspworkspace/helloworld/classes jar: [jar] Building jar: D:/work/lspworkspace/helloworld/helloworld_temp.jar BUILD SUCCESSFUL Total time: 4 seconds
|
如果你是完全copy的話,這個過程理論上是不會出錯的,如果有錯誤,請考慮是不是你的環境配置問題。
3. 第一個混淆任務
3.1. 執行混淆任務
我們按照前面講到的安裝方法,在這個可執行的Ant任務中加入混淆任務,加入的具體內容如下:
<!-- 混淆任務 --> <target name="obfuscate" depends="jar"> <taskdef name="obfuscate" classname="com.yworks.yguard.ObfuscatorTask" classpath="yguard.jar" />
<!-- 不同工程需要修改的地方 --> <obfuscate mainclass="${mainclass}" logfile="${obfuscationlog}" replaceclassnamestrings="true"> <inoutpair in="${jar}" out="${obfjar}" /> </obfuscate> </target> |
我們執行這個修改過的ant任務,在設定的輸出目錄下,至少會產生如下幾個文件,
helloworld.jar,混淆後的jar文件;
helloworld_temp.jar,混淆前的原始jar文件;
helloworld_obf_log.xml,產生的日誌文件。
下面我們打開jar包,可以發現包名、類名混淆前後是不一樣的,我們反編譯其中class文件,
這是我們在混淆之前根據編譯後的class文件反編譯產生的源文件。
package helloworld; import java.io.PrintStream; public class HelloWorld { public HelloWorld() { publicAttribute = "i am a public attrubute."; protectedAttribute = "i am a protected attrubute."; privateAttribute = "i am a private attrubute."; }
public void publicSayHello() { System.out.println("hello world in public . "); }
protected void protectedSayHello() { System.out.println("hello world in protected . "); }
private void privateSayHello() { System.out.println("hello world in private . "); }
public static void main(String args[]) { HelloWorld helloworld = new HelloWorld(); helloworld.publicSayHello(); }
public String publicAttribute; protected String protectedAttribute; private String privateAttribute; } |
這是我們根據混淆後的class文件產生的源代碼,我們可以發現類的包名、方法名、屬性名已經發生了變化。
package A; import java.io.PrintStream;
public class A { public A() { B = "i am a public attrubute."; A = "i am a protected attrubute."; C = "i am a private attrubute."; }
public void B() { System.out.println("hello world in public . "); }
protected void C() { System.out.println("hello world in protected . "); }
private void A() { System.out.println("hello world in private . "); }
public static void A(String as[]) { A a = new A(); a.B(); }
public String B; protected String A; private String C; } |
下面,我們來學習obfuscate及其相關元素。
4. Obfuscate元素
Obfuscate是整個混淆任務定義的元素,下面我們對它及其子元素進行詳細的介紹。
4.1. Obfuscate元素
在當前版本中,Obfuscate元素有如下一些屬性,
l
mainclass
:簡寫你的應用的主程序類名,主類的類名和它的主方法名都會被修改。你可能想僅僅暴露主方法(main),如果你的jar文件描述文件中MANIFEST.MF包含了
Main-Class
屬性,yguard將會自動調整成混淆後的主類名。
l logfile:混淆過程產生的日誌文件名稱,這個日誌文件包含了混淆過程的任何警告信息和映射信息。
l conservemanifest
:(取值爲boolean類型-true/false),表示混淆器是否應改保持jar包的
manifest清單文件不變。缺省值爲false,表示這個清單文件將會被混淆器修改用來反映新的信息摘要。
l
replaceClassNameStrings
:
(也是一個boolean屬性值),設定yguard是否需要取代某些硬編碼的字符串。(本文英文水平有限,這個屬性沒有理解清楚)。
4.2. inoutpair元素
inoutpair元素,每一個混淆任務(obfuscation task)都必須至少有一個inoutpair元素。該元素指定了要被混淆的源jar包和產生的目標jar包的名稱和路徑,要注意的是隻有class文件能夠被混淆,其他的如資源文件,只是簡單的從源jar包copy到目標jar包。
In屬性指定包含了需要被混淆jar包。
Out屬性指定了混淆後產生的新jar包。
4.3.
Externalclasses
元素
如果這個被混淆的jar包需要依賴其他外部class文件或者jar包,externalclasses用來指定這些被依賴的實體的具體路徑,這些實體不會被混淆。樣例代碼片如下:
<!--設置所有用到得第三方庫,不混淆它們--> <externalclasses>
<pathelement location="${third-party-lib-path}/log4j-1.2.8.jar" />
<pathelement location="${third-party-lib-path}/commons-logging-1.0.4.jar" />
<pathelement location="${third-party-lib-path}/jbossall-client.jar" />
<pathelement location="${third-party-lib-path}/commons-beanutils.jar" />
<pathelement location="${third-party-lib-path}/commons-pool.jar" />
</externalclasses>
|
4.4.
Property
元素
Property
元素用來給混淆引擎一些提示, 不同的
yGuard版本可能會有不同的提示,混淆任務可以根據這些提示來控制混淆的過程。
它有兩個強制的屬性:
l Name 混淆任務能夠理解的一個特定鍵。
l Value 鍵對應的值。
Yguard 1.5.2.0_03,支持下面的屬性:
l Error-checking屬性告訴yguard有任何錯誤的時候就終止正常任務。當前可用的值爲:
Pedantic 表示錯誤發生,混淆任務將失敗。
l
Naming-scheme
屬性告訴yguard在混淆過程中用到的命名模式,這個屬性只能取下列值之一:
Ø
Small
產生儘可能短的名字,這能使產生的結果jar包儘可能的小。
Ø
Best
產生的名字在反編譯後看起來可能象是被過去分詞化,使用該模式產生的jar包不一定能在所有的文件系統下能夠被成功的解壓,而且使用該模式也會佔用很多的空間使得結果jar很可能變的很大(通常是兩倍左右大小)。
Ø
Mix
混合了前面兩種模式,它產生的jar包有一個比較合理的大小,也很難反編譯。
l
Language-conformity
屬性告訴yguard產生大多數反編譯器能夠被反編譯的名字,換而言之,yguard產生的class文件能夠被今天多數虛擬機驗證和執行;但是在反編譯時會產生一些完全沒用的垃圾語句來混淆。該屬性當前可能的取值:
Ø
Compatible
產生的名字(包括java、jar、manifest文件),,大多數編譯器都能理解,大多數文件系統能解壓。
Ø
Legal
產生的名字一部分反編譯器可以理解。
Ø
Illegal
產生的名字可能虛擬機工作正常,但是可能使一些工具(如jbuilder)崩潰。
l
Obfuscation-prefix
用指定的包名稱混淆現在的包名稱。
l
Expose-attributes
指明除標準屬性之外的應該被暴露在外的屬性列表,缺省情況下,yguard會從方法中刪除不需要的屬性,如:deprecated。這個值可能是一個逗號分割的列表,該列表在
Section
4.7 of the VM Specification of the .class File Format中定義。 保持“deprecated”的代名片如下:
<property name="expose-attributes" value="Deprecated"/>
4.5. Expose元素
Expose是obfuscate的子元素,它用來規定classes、methods、fields、attributes這些元素是否應該被暴露。
Obfuscator 任務可能會刪除很多class文件不需要的信息,這些零散的信息可能不應該被反編譯。有一些boolean值形式的屬性來控制它們是否可以暴露,也就是不被混淆或刪除。
這些設置將影響所有的class文件,控制這些屬性的最好辦法是在classes元素中使用attribute元素。
l
sourcefile 原始源文件的名稱信息是否應該包含在被混淆的類中,缺省值爲false,表示信息將被刪除,如果取值爲true,表示原始源文件的名稱信息將保留,反編譯後一般在文件頭以註釋的方式在頭註釋的最後一行出現。
l
Linenumbertable在混淆後的類文件中是否保存原始類文件的每個操作字節碼對應的原始源文件行號信息的相關信息。缺省值爲false,表示不保存。
l localvariabletable
決定是否保留本地變量表,這個表包含了每一個在在原始源代碼中使用的本地變量名稱和混淆後的類中變量名稱的映射關係。缺省值爲false,表示將刪除該信息。
l Localvariabletypetable 是否保留本地變量類型表,這個表包含了在原始源代碼中使用的本地變量的類型與混淆後的類中變量的映射關係。缺省值爲false,不保存,也就是說,混淆過程中將刪除該信息。
4.6.
Class
元素
Class
元素用來設定如何暴露類的相關信息,這些信息包括類的名稱、方法的名稱、屬性的名稱。下面的兩個屬性來決定特定的類混淆級別。
l
Methods
屬性決定可見性在什麼級別的方法可以被暴露。
l
Fields
屬性決定可見性在何種級別的屬性字段可以被暴露。
下表列出了屬性可能的取值,以及對應於該值,類的哪些元素將被暴露。‘*’表示給定可見性的元素會被暴露,‘-’表示相應的元素將被混淆。
Value/Visibility |
public |
protected |
friendly |
private |
none |
- |
- |
- |
- |
public |
* |
- |
- |
- |
protected |
* |
* |
- |
- |
friendly |
* |
* |
* |
- |
private |
* |
* |
* |
* |
我們可以看到none表示所有的元素都會被混淆。
指定類的混淆有3中方式:
1. 使用完全限定的java類名指定特定的類。如:
<class name="mypackage.MyClass"/>
|
2.使用patternset元素設定多個java類,patternset的include和exclude元素必須符合java語法,允許使用通配符。如:
<class> <patternset> <include name="com.mycompany.**.*Bean"/> <exclude name="com.mycompany.secretpackage.*"/> <exclude name="com.mycompany.myapp.SecretBean"/> </patternset> </class>
|
上邊的代碼表示將暴露除com.mycompany.secretpackage包中類和com.mycompany.myapp.SecretBean類之外包com.mycompany及其子包中所有的類。
<class>
<patternset>
<include name="com.mycompany.myapp.MainClass"/>
<include name="org.w3c.sax?."/>
<exclude name="org.w3c.sax?.**.*$*"/>
</patternset>
</class>
|
這段代碼片表示將暴露MainClass類,org.w3c.sax1, org.w3c.sax2, org.w3c.saxb這種命名形式的包中出內部類之外的所有類。
注:‘$’符號是java中外部類和內部類的分隔符,在ant中‘$’是一個特殊字符,如果我們想把‘$’字符作爲參數傳遞給ant任務,必須使用連續兩個‘$’也就是'$$'來表示。
3.最後一種方法是我們用類的可見性來設定,如:
<class classes="protected"> <patternset> <include name="com.mycompany.myapi."/> </patternset> </class>
|
上例表示將暴露包com.mycompany.myapi及其子包中的所有public、protected元素(也就是說保留它們不變,不混淆這些元素)。
<class classes="protected" methods="protected"
fields="protected">
<patternset>
<include name="**.*"/>
</patternset>
</class>
|
這個例子演示瞭如何暴露public類型API的通用方法,表示所有的public和protected類型的類,以及它們中所有聲明爲public和protected的屬性和方法都會被暴露。
最後一個例子演示了怎麼只暴露類的公共方法,而不暴露其類名和屬性。
<class classes="none" methods="public" fields="none">
<patternset>
<include name="com.mycompany.myapi."/>
</patternset>
</class>
|
4.7. Method 元素
Method用來設定被暴露的方法簽名,一般我們用不到這樣的級別。這個元素有兩個屬性:
l class
屬性用來指定擁有該方法的類,通常使用java語法的完全限定類名,這個屬性可以被忽略,
l name
屬性指明瞭被暴露(不混淆)的方法。注意:方法的參數和返回類型必須是完全限定的java類名,即使是void類型。
一些例子如下:
<method class="com.mycompany.myapp.MyClass"
name="void main(java.lang.String[])"/>
<method class="com.mycompany.myapp.MyClass"
name="int foo(double[][], java.lang.Object)"/>
<method name="void writeObject(java.io.ObjectOutputStream)">
<patternset>
<include name="com.mycompany.myapp.data.*"/>
</patternset>
</method>
<method name="void readObject(java.io.ObjectInputStream)">
<patternset>
<include name="com.mycompany.myapp.data.*"/>
</patternset>
</method>
|
暴露MyClass 的main方法和foo方法,com.mycompany.myapp.data包中所有類的所有的writeObject和readObject方法都將被暴露,
4.8. Field元素
4.9.
Sourcefile
元素
Sourcefile 設定源文件可以暴露哪些信息,用在堆棧跟蹤上,這個元素沒有屬性,
<sourcefile>
<patternset>
<include name="com.mycompany.myapp.**"/>
</patternset>
</sourcefile>
|
包com.mycompany.myapp及其子包中所有類的類名將被暴露,注意這將阻止正常的混淆,因爲源文件的名字和未混淆的類名捆綁的很緊密。
<sourcefile>
<property name="mapping" value="y"/>
<patternset>
<include name="com.mycompany.myapp.**"/>
</patternset>
</sourcefile>
|
這將把包名稱映射成一個字母y。 (mapping屬性是當前版本sourcefile元素支持唯一屬性)。
4.10.
Linenumbertable
元素
在上邊expose元素中也有一個linenumbertable,在那裏它是一個屬性,在這裏它指定哪些類的行號表將被保留而不被混淆,主要用在堆棧跟蹤。
<linenumbertable>
<patternset>
<include name="com.mycompany.myapp.**"/>
</patternset>
</linenumbertable>
|
這將暴露com.mycompany.myapp及其子包中所有類的行號,這些類的sourcefile
屬性也必須被暴露,否則,JVM將顯示未知的行號信息Unknown
source
。
<linenumbertable>
<property name="mapping-scheme" value="scramble"/>
<property name="scrambling-salt" value="1234"/>
<patternset id="CompanyPatternSet">
<include name="com.mycompany.myapp.**"/>
</patternset>
</linenumbertable>
<sourcefile>
<property name="mapping" value="y"/>
<patternset refid="CompanyPatternSet"/>
</sourcefile>
|
4.11.
Adjust
元素
Adjust元素主要是把資源文件中引用的被混淆的類的名稱作適當的調整,這是一個比較有用的元素。
注意:它將僅僅調整inoutpair中out-jar元素設定的包,也就是混淆過後生成的jar包。它有3個屬性:
l
replaceName 屬性設定資源名稱是否應該被調整,缺省值爲false;
l
replaceContent設定資源的內容是否應該被調整,缺省值爲false;
l
replacePath 設定資源文件的路徑是否應該被調整,缺省值爲true。
一些例子如下:
<!--調整jar包中所有java屬性文件的名稱 --> <adjust replaceName="true">
<include name="**/*.properties" />
</adjust>
<!--調整jar包的某個xml文件中的引用的類名 --> <adjust file="plugins.xml" replaceContent="true" />
<!--不調整jar包中資源文件的路徑com/mycompany/myapp/resource. --> <!--包 com.mycomSpany.myapp 也可能被混淆 --> <adjust replacePath="false">
<include name="com/mycompany/myapp/resource/*" />
</adjust>
|
5. 一個實際項目中的例子
這個例子可以作爲我們大多數普通項目的模板,根據情況稍加修改就可以了。
在我的項目中,我有3個文件夾:
l Src-lib 保存未混淆前的jar包,我的文件夾下有framework-common.jar,framework-ejb.jar,cbm-common.jar,cbm-ejb.jar四個jar包需要混淆;
l Target-lib 放置混淆後產生的jar包;
l third-party-lib 放置我的項目用到的第三方庫,以及yguard混淆工具庫。
另外有一個定義ant任務的build.xml文件,文件內容如下:
<?xml version="1.0" encoding="gb2312"?>
<project name="project" default="init" basedir=".">
<!-- 設置我們Ant任務的初始化工作 --> <target name="init">
<!--工程名稱--> <property name="project_name" value="cpht" />
<!--源jar包所在路徑--> <property name="src-lib" value="src-lib" />
<!--目標jar包所在得路徑--> <property name="target-lib" value="target-lib" />
<!--yguard混淆器工作產生的日誌文件--> <property name="obfuscationlog" value="${project_name}_obf_log.xml" />
<!--第三方庫所在的路徑--> <property name="third-party-lib-path" value="third-party-lib" />
</target>
<!-- ***********************************混淆任務 **************************--> <target name="obfuscate">
<taskdef name="obfuscate" classname="com.yworks.yguard.ObfuscatorTask" classpath="${third-party-lib-path}/yguard.jar" />
<!-- 不同工程需要修改的地方 --> <obfuscate logfile="${obfuscationlog}" >
<!--有錯誤時將中斷該target--> <property name="error-checking" value="pedantic" />
<property name="language-conformity" value="illegal" />
<!--採用混合方式產生名字--> <property name="naming-scheme" value="mix" />
<!--設置需要暴露哪些信息,這些信息將不會被混淆--> <expose>
<!--僅保持類名不變,完全混淆類的其他信息--> <class classes="private" methods="none" fields="none">
<patternset id="catic">
<include name="com.catic.**" />
<include name="catic.**" />
</patternset>
</class>
<!-- 保持所有的屬性(過時的方法,文件名,行號、本地變量表),在系統允許過程中,可能會發生錯誤信息, 如果不保存這些信息,出錯時很難跟蹤代碼,爲方便調試保留了這些信息 --> <attribute name="Deprecated, SourceFile, LineNumberTable, LocalVariableTable">
<patternset refid="catic" />
</attribute>
</expose>
<!--設置所有用到得第三方庫,不混淆它們--> <externalclasses>
<pathelement location="${third-party-lib-path}/log4j-1.2.8.jar" />
<pathelement location="${third-party-lib-path}/commons-logging-1.0.4.jar" />
<pathelement location="${third-party-lib-path}/jbossall-client.jar" />
<pathelement location="${third-party-lib-path}/commons-beanutils.jar" />
<pathelement location="${third-party-lib-path}/commons-pool.jar" />
</externalclasses>
<!--輸入和輸出jar包,可以有多行--> <!--*********************輸入、輸出設置開始*************************--> <inoutpair in="${src-lib}/framework-common.jar" out="${target-lib}/framework-common-obf.jar" />
<inoutpair in="${src-lib}/framework-ejb.jar" out="${target-lib}/framework-ejb-obf.jar" />
<inoutpair in="${src-lib}/cbm-common.jar" out="${target-lib}/cbm-common-obf.jar" />
<inoutpair in="${src-lib}/cbm-ejb.jar" out="${target-lib}/cbm-ejb-obf.jar" />
<!--*********************輸入、輸出設置結束*************************-->
<!--調整jar包的資源文件中的引用的類名 --> <adjust replaceContent="true">
<include name="**/*.xml" />
<include name="./**/*.xml" />
</adjust>
</obfuscate>
<!-- 不同工程需要修改的地方 --> </target>
<!-- ***********************************混淆任務結束 ************************-->
</project>
<!-- build.xml 文件結束 --> |
第一target是設置環境變量,第二個target是執行混淆任務。
<property name="naming-scheme" value="mix" />配置混淆時產生的名字的方式爲混合方式,我們前面講到有3中方式small,best,mix,這裏我們選擇mix。
Expose元素設置了我們只保持類名不變,類的方法和屬性需要混淆,在系統運行過程中,可能會發生一些意想不到的錯誤信息,如果完全混淆類,出錯時很難跟蹤代碼,爲方便調試保留了類名、行號表、本地變量表等這些信息。
Externalclasses 元素指定了我們使用到的第三方庫。
Inoutpair元素設置我們要混淆的jar包,以及混淆後產生的對應jar包名稱和路徑。
Adjust元素調整資源文件中引用的類信息爲我們混淆後的類信息。
6. 結束語
本文只是簡單的介紹了yguard的使用,更多用法請參考官方相關的文檔。
一般來講,我們只需要作這些簡單事情就足夠了,因爲沒有任何文檔參考的情況下,沒有混淆的代碼已經不容易看懂了,如果再稍微混淆一下,相信java世界中,沒有多少人會真正花大量的精力去閱讀這種經混淆後再反編譯產生的代碼。