什么是代码混淆
-
Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java
字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。 -
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
-
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的缩短变量和函数名以及丢失部分信息的原因, 编译后 jar 文件体积大约能减少25% ,这对当前费用较贵的无线网络传输是有一定意义的。
混淆文件 proguard.cfg 参数详解
-optimizationpasses 5 # 压缩级别
-dontusemixedcaseclassnames # 不使用大小写混合
-dontskipnonpubliclibraryclasses # 混淆第三方jar
-dontpreverify # 不做预校验
-verbose # 记录日志
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 采用的算法
-keep public class * extends android.app.Activity # 保持哪些类不被混淆
-keep public class * extends android.app.Application # 保持哪些类不被混淆
-keep public class * extends android.app.Service # 保持哪些类不被混淆
-keep public class * extends android.content.BroadcastReceiver # 保持哪些类不被混淆
-keep public class * extends android.content.ContentProvider # 保持哪些类不被混淆
-keepclasseswithmembernames class * { # 保持native 方法不被混淆
native <methods>;
}
-keepclasseswithmembers class * { # 保持自定义控件类不被混淆
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int); # 保持自定义控件类不被混淆
}
-keepclassmembers class * extends android.app.Activity { # 保持自定义控件类不被混淆
public void *(android.view.View);
}
-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
-keep class MyClass; # 保持自己定义的类不被混淆
代码混淆的方法
- 高版本 SDK 下,项目中同时包含 proguard-project.txt 和 project.properties 文件,这时需要在 proguard-project.txt 文件中进行如下信息的配置,然后再将项目 Export 即可。下面以真实的文件进行演示说明。
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-18
以上的配置信息即是 project.properties 文件中内容,蓝色文字为我们在代码混淆过程中需要添加的配置信息,其中:sdk.dir 为你在当前机器上 SDK 的安装路径。如果想保留某个包下的文件不被混淆,可以在 proguard-project.txt文件中加入保留对应包名的语句即可。
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-dontwarn com.cnki.android.cnkireader.**
-keep class com.cnki.android.cnkireader.** { *; }
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-optimizationpasses 5
# 混淆时不会产生形形色色的类名
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
# 不预校验
# -dontpreverify
# 预校验
-dontoptimize
# 这1句是屏蔽警告
-ignorewarnings
-verbose
# 优化
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 这1句是导入第三方的类库,防止混淆时候读取包内容出错
#-libraryjars libs/youjar.jar
# 去掉警告
-dontwarn
-dontskipnonpubliclibraryclassmembers
# 不进行混淆保持原样
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
# 过滤R文件的混淆:
-keep class **.R$* {*;}
# 过滤第三方包的混淆:其中packagename为第三方包的包名
# -keep class packagename.** {*;}
-keep class com.hisilicon.android.hibdinfo.** {*;}
-keep class com.huawei.iptv.stb.dlna.mymedia.dto.** {*;}
-keep class com.huawei.iptv.stb.dlna.mymedia.facade.** {*;}
-keep class com.huawei.mymediaprifacade.** {*;}
-keep class com.hisilicon.android.mediaplayer.** {*;}
-keep class com.nostra13.universalimageloader.** {*;}
-keep class com.huawei.android.airsharing.** {*;}
# 所有方法不进行混淆
-keep public abstract interface com.huawei.android.airsharing.listener{
public protected <methods>;
}
-keep public abstract interface com.huawei.android.airsharing.api{
public protected <methods>;
}
# 对该方法不进行混淆
# -keep public class com.asqw.android{
# public void Start(java.lang.String);
# }
# 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
#-keepclasseswithmembernames class * {
# native <methods>;
#}
# 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在
#-keepclasseswithmembernames class * {
# public <init>(android.content.Context, android.util.AttributeSet);
#}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保护指定的类文件和类的成员
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#-libraryjars libs/android-support-v4.jar
#-dontwarn android.support.v4.**{*;}
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
#不混淆第三方包中的指定内容
-keep class android-support-v4.**{*;}
-keep public class * extends android.support.v4.**
-keep class android.view.**{*;}
ProGuard是一个免费的java类文件压缩,优化,混淆器.它探测并删除没有使用的类,字段,方法和属性.它删除没有用的说明并使用字节码得到最大优化.它使用无意义的名字来重命名类,字段和方法.
ProGuard的使用是为了:
1.创建紧凑的代码文档是为了更快的网络传输,快速装载和更小的内存占用.
2.创建的程序和程序库很难使用反向工程.
3.所以它能删除来自源文件中的没有调用的代码
4.充分利用java6的快速加载的优点来提前检测和返回java6中存在的类文件.
参数:
-include {filename} 从给定的文件中读取配置参数
-basedirectory {directoryname} 指定基础目录为以后相对的档案名称
-injars {class_path} 指定要处理的应用程序jar,war,ear和目录
-outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称
-libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-dontskipnonpubliclibraryclasses 指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。
保留选项
-keep {Modifier} {class_specification} 保护指定的类文件和类的成员
-keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
-keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件
压缩
-dontshrink 不压缩输入的类文件
-printusage {filename}
-whyareyoukeeping {class_specification}
优化
-dontoptimize 不优化输入的类文件
-assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用
-allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员
混淆
-dontobfuscate 不混淆输入的类文件
-printmapping {filename}
-applymapping {filename} 重用映射增加混淆
-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively 混淆时应用侵入式重载
-useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
-keepattributes {attribute_name,...} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
-renamesourcefileattribute {string} 设置源文件中给定的字符串常量
因为我们开发的是webwork+spring+hibernate的架构的项目,所有需要很详细的配置。(经过n次失败后总结)
Example:
-injars <project>.jar
-outjars <project>_out.jar
-libraryjars <java.home>/lib/rt.jar
-libraryjars <project.home>/webroot/WEB-INF/lib/webwork.jar
.......
# 保留实现Action接口类中的公有的,友好的,私有的属性 和 公有的,友好的方法。其它的全部压缩,优化,混淆。
# 因为配置文件中的类名是一个完整的类名,如果经过处理后就有可能找不到这个类。
# 属性是jsp页面所需要的,如果经过处理jsp页面就无法得到action中的数据。
-keep public class * implements com.opensymphony.xwork.Action{
public protected private <fields>;
public protected <methods>;
}
# 保留实现了Serializable接口类中的公有的,友好的,私有的成员(属性和方法)
# 这个配置主要是对应实体类的配置。
-keep public class * implements java.io.Serializable{
public protected private *;
}
keep选项
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
[extends|implements [@annotationtype] classname]
[{
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
(fieldtype fieldname);
[@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
<init>(argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
[@annotationtype] [[!]public|private|protected|static ... ] *;
...
}]
什么是ProGuard工具?
ProGuard是android提供的一个免费的工具,它能够移除工程中一些没用的代码,或者使用语义上隐晦的名称来重命名代码中的类、字段和函数等,达到压缩、优化和混淆代码的功能。具体来说,使用ProGuard工具,可以达到下面两个目的:
删除了源文件中没有调用的那部分代码,最大化的精简了字节码文件,使得最终生成的apk文件更小。
使用语义混淆的命名替换了代码中的类、字段和函数等,使得其他人无法反编译获取源代码,起到对代码的保护作用。
我看网上有不少人根据ProGuard工具的作用,直接称呼其为“混淆代码工具”,本文也暂时用这个词简称。
更多的理解,可以参考ProGuard工具的官方文档地址:http://developer.android.com/tools/help/proguard.html
##ProGuard工具的集成与使用环境
其实,ProGuard工具是已经集成到我们android系统中的,所以不需要用户手动的去集成。但是有一点需要注意,仅在程序处于Release模式时ProGuard才有效,反之在Debug模式是不能通过ProGuard来混淆代码的。
根据ProGuard的具体使用环境,我分在Eclipse工具和android源码两种编译环境浅谈ProGuard的使用方法
Eclipse环境中ProGuard的使用
以我电脑的android4.0环境为例,当我们在Eclipse中新建一个项目,或者导入一个已存在项目(保证当前项目没有语法错误)后,在工程的根目录,会自动生成两个ProGuard的混淆文件:proguard-project.txt和project.properties(在老版本的ADT中,只会生成一个叫proguard.cfg的文件)。我们先看下文件project.properties :
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-18
看后面一段注释:To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home) ,意指要让ProGuard 来压缩和混淆代码,把这句注释去掉即可!所以,我们只要把下面一句注释取消即可,如下所示:
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
我们仔细的看下这部分代码:这个地方是通过设置proguard.config属性来指定混淆代码的配置文件为本机SDK目录下面的proguard-android.txt文件,以及制定混淆的个性化配置文件为当前工程(eclipse下)根目录下面的proguard-project.txt文件 ,而后面这个文件,恰是我们刚才看到的原本在根目录下自动生成的另外一个文件!
其实打开了这个地方,我们就已经可以混淆代码了,不过这里要注意:不能试图通过运行eclipse中的Run as 和 Debug as 菜单来生成混淆代码,必须通过如下图所示的方法将apk导出才行,当然你可以选择“签名”或者“不签名”:
这样一步操作后,算是代码混淆完成了。那么怎么才能检验我们真的混淆了代码了呢?首先,我们能够看到在工程的根目录新生产了一个文件夹proguard,里面有四个文件,其内容如下:
dump.txt : 描述了apk中所有类 文件中内部的结构体。( Describes the internal structure of all the class files in the .apk file )
mapping.txt : 列出了原始的类、方法和名称与混淆代码见得映射。( Lists the mapping between the original and obfuscated class, method, and field names. )
seeds.txt : 列出了没有混淆的类和方法。( Lists the classes and members that are not obfuscated )
usage.txt : 列出apk中删除的代码。( Lists the code that was stripped from the .apk )
同时使用反编译软件对新生成的apk反编译后会发现,里面的类名、方法和变量等,都变成了简单的a、b、c、d等毫无含义的字母,这样就达到了混淆的目的:
但在实际使用过程中,我们会发现当前apk中的有些方法和类,是要供外部使用的,而此时混淆了名称,外部调用就会报错了,那么怎么解决这个问题?此时就要用到我们刚才提到的混淆的个性化配置文件proguard-project.txt,在其中去配置不需要混淆的类、方法和变量等。关于混淆文件的具体配置方法,请看下面的最后一个标题会有详述。
Android源码环境中ProGuard使用
在Google发布的android源码中,面对那么多代码和文件目录,此时该如何混淆代码与配置混淆文件呢?
android中默认是将代码混淆ProGuard关闭的,在alps/build/core/proguard.flags中有下面一句,意指将默认不混淆,不需要代码删除,我们将这一句注释起来,就起到代码混淆编译的作用。
# Don't obfuscate. We only need dead code striping.
-dontobfuscate
可以说,这句是android工程中代码混淆的总开关,然而,注释了上面的代码后,整个工程就已经是代码混淆了吗?不是的,这里还要关注一个文件alps/build/core/package.mk,在这个文件中有这么一段:
ifndef LOCAL_PROGUARD_ENABLED
ifneq ($(filter user userdebug, $(TARGET_BUILD_VARIANT)),)
# turn on Proguard by default for user & userdebug build
#LOCAL_PROGUARD_ENABLED :=full
endif
endif
切记:当我们需要对整个工程进行代码混淆的时候,就把此处的 #LOCAL_PROGUARD_ENABLED :=full注释去掉,成为有效的宏即可。如果不想对整个工程代码混淆,而只是相对某个模块混淆的话,就先不要动这里的代码。
接着建议将真个工程new一遍,之后就可以针对具体的apk文件进行混淆文件的设置和定制了。下面以alps/packages/apps/Music为例说说该如何对特定模块做到混淆代码:
在Music目录下,我们看到一个平时不太关注,但今天一定很在意的文件名:proguard.flags ,对了,这个文件就是Music的混淆配置文件,可以打开看看(有些地方没有这个文件,用户可以自己手动新建一下,最好名称也叫proguard.flags,android下默认都是这个名字)。当然,设置了配置文件还是不够的,还需要在同目录的Android.mk中如下设置如下两句:
LOCAL_PROGUARD_ENABLED := full
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
只有这样才能让混淆代码有效,并且将混淆配置文件关联起来。(有些模块没有这两句句,就自己手动加上)
反之,如果用户已经在alps/build/core/package.mk打开了全工程混淆编译的控制点后,又在针对某个模块时不想混淆编译怎么办?这就简单了,将模块下的Android.mk中设置为**LOCAL_PROGUARD_ENABLED := disabled**即可。
这样,我们通过mm编译后的代码生成的apk,或者new真个工程后生成的烧机代码,都是已经添加相应配置的混淆代码了。
反编译后,除过proguard.flags中定制的不需要混淆的代码外,其他都是被混淆了,如图所示是android中Music模块的混淆后反编译结果:
混淆文件的配置
在实际使用过程中,我们会发现当前apk中的有些方法和类,是要供外部使用的,而此时混淆了名称,外部调用就会报错了,那么怎么解决这个问题?此时就需要我们配置混淆的个性化文件proguard-project.txt(eclipse环境中)或者proguard.flags(android源码环境),在其中去配置不需要混淆的类、方法和变量等。关于混淆文件的具体配置方法,大家可以去搜索下,我这里提供一段网上有人共享的配置代码,这个配置代码保留了工程文件中的Activity、Application、Service、BroadcastReceiver、ContentProvider、BackupAgentHelper、Preference和ILicensingService的子类,并保留了所有的Native变量名及类名,所有类中部分以设定了固定参数格式的构造函数,枚举等等,用来防止外部调用出错,大家可以借鉴下,以后来配置自己的文件:
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}