Android 專案的程式碼混淆,Android proguard 使用說明
簡介
Java程式碼是非常容易反編譯的。為了很好的保護Java原始碼,我們往往會對編譯好的class檔案進行混淆處理。
ProGuard是一個混淆程式碼的開源專案。它的主要作用就是混淆,當然它還能對位元組碼進行縮減體積、優化等,但那些對於我們來說都算是次要的功能。
官網地址:http://proguard.sourceforge.net/
原理
Java 是一種跨平臺的、解釋型語言,Java 原始碼編譯成中間”位元組碼”儲存於 class 檔案中。由於跨平臺的需要,Java 位元組碼中包括了很多原始碼資訊,如變數名、方法名,並且通過這些名稱來訪問變數和方法,這些符號帶有許多語義資訊,很容易被反編譯成 Java 原始碼。為了防止這種現象,我們可以使用 Java 混淆器對 Java 位元組碼進行混淆。
混淆就是對釋出出去的程式進行重新組織和處理,使得處理後的程式碼與處理前程式碼完成相同的功能,而混淆後的程式碼很難被反編譯,即使反編譯成功也很難得出程式的真正語義。被混淆過的程式程式碼,仍然遵照原來的檔案格式和指令集,執行結果也與混淆前一樣,只是混淆器將程式碼中的所有變數、函式、類的名稱變為簡短的英文字母代號,在缺乏相應的函式名和程式註釋的況下,即使被反編譯,也將難以閱讀。同時混淆是不可逆的,在混淆的過程中一些不影響正常執行的資訊將永久丟失,這些資訊的丟失使程式變得更加難以理解。
混淆器的作用不僅僅是保護程式碼,它也有精簡編譯後程序大小的作用。由於以上介紹的縮短變數和函式名以及丟失部分資訊的原因, 編譯後 jar 檔案體積大約能減少25% ,這對當前費用較貴的無線網路傳輸是有一定意義的。
語法
-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} -dontwarn 如果有警告也不終止 -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} 設定原始檔中給定的字串常量
Android Eclipse開發環境與ProGuard
在Android 2.3以前,混淆Android程式碼只能手動新增proguard來實現程式碼混淆,非常不方便。而2.3以後,Google已經將這個工具加入到了SDK的工具集裡。具體路徑:SDK\tools\proguard。當建立一個新的Android工程時,在工程目錄的根路徑下,會出現一個proguard的配置檔案proguard.cfg。也就是說,我們可以通過簡單的配置,在我們的elipse工程中直接使用ProGuard混淆Android工程。
具體混淆的步驟非常簡單。首先,我們需要在工程描述檔案project.properties中,新增一句話,啟用ProGuard。如下所示:
# 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-19
這樣,Proguard就可以使用了。當我們正常通過Android Tools匯出Application Package時(或者使用ant執行release打包),Proguard就會自動啟用,優化混淆你的程式碼。匯出成功後,你可以反編譯看看混淆的效果。一些類名、方法名和變數名等,都變成了一些無意義的字母或者數字。證明混淆成功!
例項(proguard 檔案程式碼解讀)
-optimizationpasses 7 #指定程式碼的壓縮級別 0 - 7 -dontusemixedcaseclassnames #是否使用大小寫混合 -dontskipnonpubliclibraryclasses #如果應用程式引入的有jar包,並且想混淆jar包裡面的class -dontpreverify #混淆時是否做預校驗(可去掉加快混淆速度) -verbose #混淆時是否記錄日誌(混淆後生產對映檔案 map 類名 -> 轉化後類名的對映 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #淆採用的演算法 -keep public class * extends android.app.Activity #所有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 -keep public class com.android.vending.licensing.ILicensingService #指定具體類不要去混淆 -keepclasseswithmembernames class * { native <methods>; #保持 native 的方法不去混淆 } -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); #保持指定規則的方法不被混淆(Android layout 佈局檔案中為控制元件配置的onClick方法不能混淆) } -keep public class * extends android.view.View { #保持自定義控制元件指定規則的方法不被混淆 public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); public void set*(...); } -keepclassmembers enum * { #保持列舉 enum 不被混淆 public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { #保持 Parcelable 不被混淆(aidl檔案不能去混淆) public static final android.os.Parcelable$Creator *; } -keepnames class * implements java.io.Serializable #需要序列化和反序列化的類不能被混淆(注:Java反射用到的類也不能被混淆) -keepclassmembers class * implements java.io.Serializable { #保護實現介面Serializable的類中,指定規則的類成員不被混淆 static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient <fields>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keepattributes Signature #過濾泛型(不寫可能會出現型別轉換錯誤,一般情況把這個加上就是了) -keepattributes *Annotation* #假如專案中有用到註解,應加入這行配置 -keep class **.R$* { *; } #保持R檔案不被混淆,否則,你的反射是獲取不到資源id的 -keep class **.Webview2JsInterface { *; } #保護WebView對HTML頁面的API不被混淆 -keepclassmembers class * extends android.webkit.WebViewClient { #如果你的專案中用到了webview的複雜操作 ,最好加入 public void *(android.webkit.WebView,java.lang.String,android.graphics.Bitmap); public boolean *(android.webkit.WebView,java.lang.String); } -keepclassmembers class * extends android.webkit.WebChromeClient { #如果你的專案中用到了webview的複雜操作 ,最好加入 public void *(android.webkit.WebView,java.lang.String); } #對WebView的簡單說明下:經過實戰檢驗,做騰訊QQ登入,如果引用他們提供的jar,若不加防止WebChromeClient混淆的程式碼,oauth認證無法回撥,反編譯基程式碼後可看到他們有用到WebChromeClient,加入此程式碼即可。 -keepclassmembernames class com.cgv.cn.movie.common.bean.** { *; } #轉換JSON的JavaBean,類成員名稱保護,使其不被混淆 ################################################################## # 下面都是專案中引入的第三方 jar 包。第三方 jar 包中的程式碼不是我們的目標和關心的物件,故而對此我們全部忽略不進行混淆。 ################################################################## -libraryjars libs/android-support-v4.jar -dontwarn android.support.v4.** -keep class android.support.v4.** { *; } -keep interface android.support.v4.** { *; } -keep public class * extends android.support.v4.** -keep public class * extends android.app.Fragment -libraryjars libs/gson-2.3.1-sources.jar -libraryjars libs/gson-2.3.1.jar -dontwarn com.google.gson.** -keep class sun.misc.Unsafe { *; } -keep class com.google.gson.** { *; } -libraryjars libs/alipaySDK-20150602.jar -dontwarn com.alipay.** -dontwarn com.ta.utdid2.** -dontwarn com.ut.device.** -keep class com.alipay.** { *; } -keep class com.ta.utdid2.** { *; } -keep class com.ut.device.** { *; } -libraryjars libs/android-async-http-1.4.6.jar -dontwarn com.loopj.android.http.** -keep class com.loopj.android.http.** { *; } -libraryjars libs/baidumapapi_v2_4_1.jar -dontwarn com.baidu.** -keep class com.baidu.** {*; } -keep class assets.** {*; } -keep class vi.com.gdi.bgl.** {*; } -libraryjars libs/libammsdk.jar -dontwarn com.tencent.** -keep class com.tencent.** { *; } -libraryjars libs/locSDK_4.1.jar -dontwarn com.baidu.location.** -keep class com.baidu.location.** { *; } -libraryjars libs/mframework.jar -dontwarn m.framework.** -keep class m.framework.** { *; } -libraryjars libs/mta-sdk-1.6.2.jar -dontwarn com.tencent.stat.** -keep class com.tencent.stat.** { *; } -libraryjars libs/nineoldandroids-library-2.4.0.jar -dontwarn com.nineoldandroids.** -keep class com.nineoldandroids.** { *; } -libraryjars libs/open_sdk_r4889.jar -dontwarn com.tencent.** -keep class com.tencent.** { *; } -libraryjars libs/ShareSDK-Core-2.5.9.jar -dontwarn cn.sharesdk.framework.** -keep class cn.sharesdk.framework.** { *; } -libraryjars libs/ShareSDK-ShortMessage-2.5.9.jar -dontwarn cn.sharesdk.system.text.** -keep class cn.sharesdk.system.text.** { *; } -libraryjars libs/ShareSDK-SinaWeibo-2.5.9.jar -dontwarn cn.sharesdk.sina.weibo.** -keep class cn.sharesdk.sina.weibo.** { *; } -libraryjars libs/ShareSDK-Wechat-2.5.9.jar -dontwarn cn.sharesdk.wechat.friends.** -keep class cn.sharesdk.wechat.friends.** { *; } -libraryjars libs/ShareSDK-Wechat-Core-2.5.9.jar -dontwarn cn.sharesdk.wechat.utils.** -keep class cn.sharesdk.wechat.utils.** { *; } -libraryjars libs/ShareSDK-Wechat-Favorite-2.5.9.jar -dontwarn cn.sharesdk.wechat.favorite.** -keep class cn.sharesdk.wechat.favorite.** { *; } -libraryjars libs/ShareSDK-Wechat-Moments-2.5.9.jar -dontwarn cn.sharesdk.wechat.moments.** -keep class cn.sharesdk.wechat.moments.** { *; } -libraryjars libs/universal-image-loader-1.9.2-SNAPSHOT-with-sources.jar -dontwarn com.nostra13.universalimageloader.** -keep class com.nostra13.universalimageloader.** { *; } -libraryjars libs/weibosdkcore.jar -dontwarn com.sina.weibo.sdk.** -keep class com.sina.weibo.sdk.** { *; }
關於如何配置忽略第三方jar,附上一個圖進行說明。
說明一下,第三方jar包中如果有.so檔案,不用去理會,引入的第三方jar檔案不要混淆,否則可能會報異常。
檔案
在release模式下打包apk時會自動執行ProGuard,這裡的release模式指的是通過ant release命令或eclipse project->android tools->export signed(unsigned) application package生成apk。
在debug模式下為了更快除錯並不會呼叫proguard。
如果是ant命令打包apk,proguard資訊檔案會保存於工程程式碼下的/bin/proguard資料夾內;
如果用eclipse export命令打包,會在/proguard資料夾內。其中包含以下檔案:
mapping.txt
表示混淆前後程式碼的對照表,這個檔案非常重要。如果你的程式碼混淆後會產生bug的話,log提示中是混淆後的程式碼,希望定位到原始碼的話就可以根據mapping.txt反推。
每次釋出都要保留它方便該版本出現問題時調出日誌進行排查,它可以根據版本號或是釋出時間命名來儲存或是放進程式碼版本控制中。
dump.txt
描述apk內所有class檔案的內部結構。
seeds.txt
列出了沒有被混淆的類和成員。
usage.txt
列出了原始碼中被刪除在apk中不存在的程式碼。
不能混淆的程式碼
顧名思義,不能混淆程式碼如果被混淆了,就會出現錯誤。
1、需要反射的程式碼
2、系統介面
3、Jni介面
4、需要序列號和反序列化的程式碼(即實現Serializable介面的JavaBean)
5、與服務端進行元資料互動的JavaBean(JSON、XML中對應的類)
……
常見錯誤
1) Proguard returned with error code 1. See console
> 更新proguard版本
> android-support-v4 不進行混淆
> 新增缺少相應的庫
2) 使用gson包解析資料時,出現 missing type parameter 異常
> 在 proguard-project.txt 中新增
-dontobfuscate
-dontoptimize
> 在 proguard-project.txt 中新增
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }
3) 型別轉換錯誤
> 在 proguard-project.txt 中新增
-keepattributes Signature
4) 空指標異常
> 混淆過濾掉相關類與方法
5) java.lang.reflect.UndeclaredThrowableException
> -keep interface com.dev.impl.**
6) Error: Unable to access jarfile ..libproguard.jar
> 路徑問題
7) java.lang.NoSuchMethodError
> 這也是最常見的問題,因為找不到相關方法,方法被混淆了,混淆過濾掉相關方法便可。
----------------------
(完)