在某些情况下, 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 脚本中直接插入下面的代码片。
classpath="yguar d.jar" / >
|
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 任务中加入混淆任务,加入的具体内容如下:
|
我们执行这个修改过的 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 用来指定这些被依赖的实体的具体路径,这些实体不会被混淆。样例代码片如下:
|
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” 的代名片如下:
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 类名指定特定的类。如:
|
2. 使用 patternset 元素设定多个java 类,patternset 的include 和exclude 元素必须符合java 语法,允许使用通配符。如:
|
上边的代码表示将暴露除 com.mycompany.secretpackage 包中类和 com.mycompany.myapp.SecretBean 类之外包 com.mycompany 及其子包中所有的类。
|
这段代码片表示将暴露MainClass 类,org.w3c.sax1, org.w3c.sax2, org.w3c.saxb 这种命名形式的包中出内部类之外的所有类。
注:‘$ ’符号是java 中外部类和内部类的分隔符,在ant 中‘$ ’是一个特殊字符,如果我们想把‘$ ’字符作为参数传递给ant 任务,必须使用连续两个‘$ ’也就是'$$' 来表示。
3 .最后一种方法是我们用类的可见性来设定,如:
|
上例表示将暴露包com.mycompany.myapi 及其子包中的所有public 、protected 元素(也就是说保留它们不变,不混淆这些元素)。
fields="protected">
|
这个例子演示了如何暴露public 类型API 的通用方法,表示所有的public 和protected 类型的类,以及它们中所有声明为public 和protected 的属性和方法都会被暴露。
最后一个例子演示了怎么只暴露类的公共方法,而不暴露其类名和属性。
|
4.7. Method 元素
Method 用来设定被暴露的方法签名,一般我们用不到这样的级别。这个元素有两个属性:
l class
属性用来指定拥有该方法的类,通常使用java 语法的完全限定类名,这个属性可以被忽略,
l name
属性指明了被暴露(不混淆)的方法。注意:方法的参数和返回类型必须是完全限定的java 类名,即使是void 类型。
一些例子如下:
name="void main(java.lang.String[])"/>
name="int foo(double[][], java.lang.Object)"/>
|
暴露MyClass 的main 方法和foo 方法,com.mycompany.myapp.data 包中所有类的所有的writeObject 和readObject 方法都将被暴露,
4.8. Field 元素
4.9.
Sourcefile
元素
Sourcefile 设定源文件可以暴露哪些信息,用在堆栈跟踪上,这个元素没有属性,
|
包com.mycompany.myapp 及其子包中所有类的类名将被暴露,注意这将阻止正常的混淆,因为源文件的名字和未混淆的类名捆绑的很紧密。
|
这将把包名称映射成一个字母y 。 (mapping 属性是当前版本sourcefile 元素支持唯一属性)。
4.10.
Linenumbertable
元素
在上边expose 元素中也有一个linenumbertable ,在那里它是一个属性,在这里它指定哪些类的行号表将被保留而不被混淆,主要用在堆栈跟踪。
|
这将暴露com.mycompany.myapp 及其子包中所有类的行号,这些类的sourcefile
属性也必须被暴露,否则,JVM 将显示未知的行号信息Unknown source
。
|
4.11.
Adjust
元素
Adjust 元素主要是把资源文件中引用的被混淆的类的名称作适当的调整,这是一个比较有用的元素。
注意:它将仅仅调整inoutpair 中out-jar 元素设定的包,也就是混淆过后生成的jar 包。它有3 个属性:
l
replaceName 属性设定资源名称是否应该被调整,缺省值为false ;
l
replaceContent 设定资源的内容是否应该被调整,缺省值为false ;
l
replacePath 设定资源文件的路径是否应该被调整,缺省值为true 。
一些例子如下:
|
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 文件,文件内容如下:
|
第一target 是设置环境变量,第二个target 是执行混淆任务。
配置混淆时产生的名字的方式为混合方式,我们前面讲到有3
中方式small,best
,mix
,这里我们选择mix
。
Expose
元素设置了我们只保持类名不变,类的方法和属性需要混淆,在系统运行过程中,可能会发生一些意想不到的错误信息,如果完全混淆类,出错时很难跟踪代码,为方便调试保留了类名、行号表、本地变量表等这些信息。
Externalclasses
元素指定了我们使用到的第三方库。
Inoutpair
元素设置我们要混淆的jar
包,以及混淆后产生的对应jar
包名称和路径。
Adjust
元素调整资源文件中引用的类信息为我们混淆后的类信息。
6. 结束语
本文只是简单的介绍了yguard
的使用,更多用法请参考官方相关的文档。
一般来讲,我们只需要作这些简单事情就足够了,因为没有任何文档参考的情况下,没有混淆的代码已经不容易看懂了,如果再稍微混淆一下,相信java
世界中,没有多少人会真正花大量的精力去阅读这种经混淆后再反编译产生的代码。