
前言
當(dāng)我們?nèi)f分努力將項(xiàng)目開(kāi)發(fā)完成之后,提交最后一行代碼后,會(huì)怎么樣?
長(zhǎng)舒一口氣,終于完成了,給自己放一天假,休息一下吧?
不,還沒(méi)完,作為一個(gè) Android 開(kāi)發(fā)者,我們接下來(lái)做的事情還有不少呢。
第一步 :混淆和加固應(yīng)用
實(shí)不相瞞,我之前從未對(duì)我的應(yīng)用加固過(guò),因?yàn)槲腋杏X(jué)用戶量不是太大,也不會(huì)有人有興趣來(lái)反編譯或者攻擊。直到我看到這一句話:
當(dāng)用戶使用你的應(yīng)用的那一刻起,作為開(kāi)發(fā)者,你就有責(zé)任去保證應(yīng)用使用整個(gè)過(guò)程中的用戶設(shè)備和信息的安全。
回想之前,覺(jué)得自己真的好爛,從現(xiàn)在開(kāi)始,我想成為一個(gè)負(fù)責(zé)任的開(kāi)發(fā)者。
第二步:打各種渠道包,看吶,這百花齊放的應(yīng)用市場(chǎng)。
因?yàn)閭ゴ蟮膲Γ瑳](méi)有了 Google Play ,但是我們收獲了一大片森林,多少個(gè)渠道包,取決于我們的運(yùn)營(yíng)是有多大的野心,一般情況下,幾十個(gè)是少不了的。
混淆和加固的區(qū)別
首先要明確的,混淆和加固是兩個(gè)非常不同的概念。(明確的原因,在于我之前就傻傻的以為混淆和加固是一回事,掩面羞愧逃~ )
混淆,是一種類似障眼法的作用,讓反編譯后的代碼閱讀難度增加,本質(zhì)上來(lái)說(shuō),并非是防止了反編譯,而是增加了閱讀難度。例如將要混淆類名和函數(shù)名,替換為無(wú)意義的短名稱,(OrderUtils. createOrder() -> A.b() )。
加固,可以理解為,將APK的外層加了一層殼,如果想反編譯,必須突破這層殼的保護(hù)。加固后的APK,反編譯出來(lái),看到的只是外面那層殼的代碼。加固涉及到的技術(shù)手段就很多了,同時(shí)也非常的深?yuàn)W,例如dex 文件加密和字節(jié)碼變形等。
混淆
開(kāi)啟混淆很簡(jiǎn)單
說(shuō)到混淆吧,沒(méi)用過(guò)混淆的人,看到混淆的那些關(guān)鍵字,估計(jì)都和我之前一樣,簡(jiǎn)直一臉懵,但是混淆過(guò)的我,告訴你們,完全不要害怕,混淆的道理真的很簡(jiǎn)單。
混淆,主要包括三個(gè)部分,資源壓縮,代碼混淆和代碼壓縮。
資源壓縮
主要壓縮的是對(duì)象是項(xiàng)目 res 和 asset 文件下未被引用的資源文件。這個(gè)工作是通過(guò) Gradle 的 Android 插件去實(shí)現(xiàn),默認(rèn)資源壓縮是關(guān)閉的,我們?cè)?build.gradle 中可以通過(guò)shrinkResources true
來(lái)開(kāi)啟。代碼壓縮和代碼混淆
上面針對(duì)的是資源,這個(gè)針對(duì)的就是代碼文件了。這個(gè)方面,有一個(gè)在專門做 Java 字節(jié)碼混淆的工具 ProGuard ,使用起來(lái)非常方便,是不是很開(kāi)心?更開(kāi)心的是 Android Studio 2.0 之后已經(jīng)集成 ProGuard,我們?cè)俅慰梢酝ㄟ^(guò)在 build.gradle 中通過(guò)minifyEnabled true
就可以開(kāi)啟混淆了。
所以 build.gradle 中添加短短的三行代碼,項(xiàng)目打包 release 包就已經(jīng)是混淆后的包了。
buildTypes {
release {
minifyEnabled true //開(kāi)啟混淆和代碼壓縮
shrinkResources true //開(kāi)啟資源壓縮
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
這里注意 shrinkResources true 在 minifyEnabled true 的前提下,才會(huì)生效。因?yàn)樾枰a壓縮,釋放掉無(wú)用資源的引用,資源壓縮才能正常工作。
思考->為什么需要自定義規(guī)則,不能自動(dòng)混淆好嗎?
這是上面混淆三行代碼的中最長(zhǎng)的一句:
//混淆規(guī)則的文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
上面代碼中有涉及到兩個(gè)文件,其中
proguard-android.txt 是 Android SDK 提供的默認(rèn)混淆文件,里面有 Android 應(yīng)用通用的一些混淆規(guī)則,位置在 sdk/tools/proguard/ 中,這個(gè)文件不需要我們修改,默認(rèn)就好。
proguard-rules.pro 是項(xiàng)目的混淆文件,提供給開(kāi)發(fā)者,用來(lái)編寫自定義的混淆規(guī)則,這個(gè)文件,才是我們做混淆工作的主戰(zhàn)場(chǎng)。
寫之前,需要明確的是,我們?yōu)槭裁匆?proguard-rules.pro中自定義規(guī)則?
這個(gè)需要明白兩個(gè)問(wèn)題
1.ProGuard 是怎么做到混淆的?
壓縮: 移除無(wú)效的類、屬性、方法等
優(yōu)化: 優(yōu)化字節(jié)碼,并刪除未使用的結(jié)構(gòu)
混淆: 將類名、屬性名、方法名混淆為難以讀懂的字母,比如a,b,c
2.ProGuard 混淆的范圍?是全部的類嗎?
ProGuard 是一個(gè)優(yōu)化 Java 字節(jié)碼的工具,也就是說(shuō),只要是Java文件都會(huì)被混淆。
但是這并不是我們想要的結(jié)果,因?yàn)橛行╊悾绻换煜蜁?huì)出現(xiàn)問(wèn)題,舉個(gè)例子,布局中有一個(gè) button 的 android: onClick 引用了 Activity MainActivity 中的方法 buttonClick()。混淆之后,布局文件是 .xml 文件不會(huì)被混淆,保持不變,Activity類是 .java 文件,會(huì)被混淆 ,MainActivity 被混淆成 A,buttonClick()被混淆成 b()。然后,讓我們點(diǎn)擊 button ,觸發(fā) onClick ,找不到 MainActivity.buttonClick(),是不是就會(huì)報(bào)錯(cuò)了,干脆利落地 Crash 了。
造成這個(gè)結(jié)果的原因是,ProGuard 并不是專門用來(lái)優(yōu)化 Android 應(yīng)用的一個(gè)工具,所以它并不知道什么該混淆,什么不該混淆。
混淆規(guī)則的制訂,更多的是一種保護(hù),告訴 ProGuard 某些類某些方法不用混淆。
proguard-android.txt 中默認(rèn)的一些規(guī)則,就是針對(duì)所有的 Android 項(xiàng)目都適用的混淆規(guī)則。
例如:
#包名不混合大小寫
-dontusemixedcaseclassnames
#不跳過(guò)非公共的庫(kù)的類
-dontskipnonpubliclibraryclasses
#混淆時(shí)記錄日志
-verbose
#關(guān)閉預(yù)校驗(yàn)
-dontpreverify
#不優(yōu)化輸入的類文件
-dontoptimize
#保護(hù)注解
-keepattributes *Annotation*
//等等,全部?jī)?nèi)容請(qǐng)自己去看sdk/tools/proguard/proguard-android.txt
由于每個(gè)項(xiàng)目的情況都不一樣,所以 項(xiàng)目中 proguard-rules.pro 的文件,就是留給開(kāi)發(fā)者根據(jù)自身項(xiàng)目情況,去設(shè)置混淆規(guī)則的。可謂是非常人性化。
混淆的99%工作->編寫自定義混淆規(guī)則
自定義混淆規(guī)則文件 proguard-rules.pro 的內(nèi)容可以分為三個(gè)部分:
- 一些前輩們總結(jié)的,一般項(xiàng)目都會(huì)用到的混淆規(guī)則
- 第三方庫(kù)的混淆規(guī)則 ,這個(gè)第三方文檔上都有。
- 如果有使用ORM類型的數(shù)據(jù)庫(kù),例如greenDao,需要保護(hù)映射數(shù)據(jù)表的實(shí)體類不被混淆
- 保護(hù) JNI 中調(diào)用的類不被混淆。
- 保護(hù) WebView 中 JavaScript 調(diào)用的方法不被混淆
- 保護(hù) Layout 布局使用的View構(gòu)造函數(shù)、android:onClick等不被混淆。
關(guān)于怎么保護(hù),這個(gè)按照 ProGuard 制訂規(guī)則去保護(hù)了,關(guān)鍵詞很多,而且視項(xiàng)目不同,混淆文件一般也不一樣,我也沒(méi)法給你提供一個(gè)模板什么。
你可以看這篇文章 寫給 Android 開(kāi)發(fā)者的混淆使用手冊(cè),寫的超級(jí)詳細(xì)超級(jí)棒,我就是看這個(gè)學(xué)會(huì)的混淆。
加固
加固這方面的技術(shù)要求太高了,沒(méi)有特別的安全需求的話,都會(huì)選擇第三方加固平臺(tái),公司項(xiàng)目采用的是梆梆加固。
梆梆加固的使用方式很簡(jiǎn)單,上傳 apk 包,點(diǎn)擊加固,等待加固完畢,當(dāng)加固成功后,會(huì)得到一個(gè)評(píng)估報(bào)告,要仔細(xì)閱讀一下,查看是否由代碼上可修改的漏洞等,如果有,修改后再加固,然后,下載加固后的加固包即可。
多渠道打包
只要是需要分發(fā)到各大市場(chǎng)的應(yīng)用,多渠道的統(tǒng)計(jì)必不可少,因此多渠道打包必不可少,公司項(xiàng)目采用多渠道打包工具是 PackerNg。
PackerNgV2 提供 gradle 集成打包和腳本打包,因?yàn)槲覀冃枰庸蹋赃x擇腳本打包方式。具體步驟可以看 PackerNg的文檔
完整打包流程
基本的打包流程如下:
<font color="#008000">注意看,除了上面介紹的部分,多了一個(gè)重新簽名的步驟。</font>
因?yàn)楫?dāng)加固之后的 Apk 沒(méi)有簽名,需要我們重新簽名。自動(dòng)簽名的工具有很多,包括梆梆加固都有提供再簽名的工具,使用這些簽名之后PackerNg 腳本打包后會(huì)出現(xiàn) Error: Invalid Signature
錯(cuò)誤。原因是現(xiàn)在提供的工具大多只支持了 V1 簽名,而項(xiàng)目集成的 PackerNg 最新版本需要 V2 簽名,所以我們要給 Apk 重新簽 V2 簽名。
命令如下:
zipalign -v 4 <apk_path> <after_apk_path>//對(duì)齊
apksigner sign --ks <keystore_path> <after_apk_path> //重新簽名
//以下命令可用來(lái)驗(yàn)證對(duì)齊和再簽名的結(jié)果
zipalign -c -v 4 <apk_path>//驗(yàn)證是否對(duì)齊
apksigner verify --verbose <after_apk_path> //查看簽名信息,用來(lái)驗(yàn)證是否是 V2 簽名
其中
apksigner 是Android SDK 自24.0.3開(kāi)始提供的官方簽名工具,位于:Android SDK/build-tools/對(duì)應(yīng)版本/apksigner。
zipalign 是 Android SDK 提供代碼對(duì)齊工具, 位于 Android SDK/build-tools/對(duì)應(yīng)版本/zipalign。
如果已經(jīng)配置好 sdk/build-tools/的 Path,直接在 Terminal 中,任何路徑下就能使用,如果沒(méi)有可以選擇是配置一下路徑或者切換到 sdk/build-tools/ 對(duì)應(yīng)版本/ 中操作。
最后
真的很感嘆,我也終于自己完整的走通了一個(gè)項(xiàng)目到發(fā)布市場(chǎng)的一整套流程,現(xiàn)在做完之后,看起來(lái)一切都很簡(jiǎn)單,步驟也不是很多,但是對(duì)于上周的我來(lái)說(shuō),因?yàn)閷?duì)混淆和加固的知之甚少,真的膽戰(zhàn)心驚的去做這件事情,混淆之后安裝測(cè)試,簽名之后安裝測(cè)試等等,因?yàn)榧s了百度的首發(fā),上線時(shí)間有限制,真的害怕搞出來(lái)自己短時(shí)間內(nèi)無(wú)法解決的問(wèn)題。
我仔細(xì)想想,當(dāng)時(shí)的緊張,都是對(duì)未知的恐懼。例如,簽名遇到問(wèn)題了,才知道 Android 7.0 新出了 V2 簽名機(jī)制等,因?yàn)闆](méi)用過(guò),也測(cè)了又測(cè),直到放心。
在這個(gè)知識(shí)的海洋里,我知道的真是太少了,努力加油吧,為了以能夠自信多一點(diǎn)點(diǎn)。
剛剛開(kāi)通了個(gè)人微信公眾號(hào),最新的博客,好玩的事情,都會(huì)在上面分享,歡迎關(guān)注 ^ o ^ 。