前端代碼因為需要直接傳輸到用戶端執行,是以代碼混淆技術較早的開始發展,目前比較成熟。後端代碼長期以來混淆的需求并不突出,然而随着Java代碼需要被客戶接觸到,并不放在公司完全受控的環境,如以apk形式在使用者手機上或以應用形式在專有雲中,是以後端代碼混淆提到了日程中。
成熟的Java混淆工具很多,如下表:
名稱
授權
首頁
yGuard
LGPL
<a href="http://www.yworks.com/products/yguard">http://www.yworks.com/products/yguard</a>
ProGuard
GPLv2
<a href="https://www.guardsquare.com/en/proguard">https://www.guardsquare.com/en/proguard</a>
Facebook ProGuard分支
<a href="https://github.com/facebook/proguard">https://github.com/facebook/proguard</a>
DashO
Commercial
<a href="https://www.preemptive.com/products/dasho">https://www.preemptive.com/products/dasho</a>
Allatori
<a href="http://www.allatori.com">http://www.allatori.com</a>
Stringer
<a href="https://jfxstore.com">https://jfxstore.com</a>
Java Antidecompiler
<a href="http://www.bisguard.com/help/java/">http://www.bisguard.com/help/java/</a>
Zelix KlassMaster
<a href="http://www.zelix.com">http://www.zelix.com</a>
一般初步學習适用從開源免費的軟體開始,那麼我們就從yGuard和ProGuard兩者來比較,首先看Google搜尋:
很顯然ProGuard更加活躍。從混淆情況看,既然是混淆工具,混淆上差别不大,yGuard基于Ant Task,是以在maven中需要maven-antrun-plugins來支援,并且需要寫ant task腳本。ProGuard有proguard-maven-plugin + 配置檔案的形式,更加友善。同時ProGuard有Facebook ProGuard的Folk版本,和DexGuard商業版本兩個較活躍的衍生版本,支援整個生态良好發展。是以我們選擇ProGuard。
因為我們的應用主要是面向專有雲的Java EE應用,是以這裡不考慮安卓apk什麼事了。複雜的JavaEE應用一般是多module的,可能涉及不同module的jar包依賴、各種寫着類名的配置檔案,但用到反射的情況并不多,主要是某些AOP、hack之類的。是以需要小心的混淆,了解混淆的每一個配置及可能帶來的副作用。這裡我們僅僅對代碼進行适度的混淆,示例中并沒有考慮應用中的反射,但一般場景下已經足夠。
假設應用名稱是<code>$APP_NAME</code>,應用名稱與IDE裡項目名稱相同,項目下有一些子子產品(Module),名叫module-1、module-2……,應用代碼都屬于<code>com.company.appname</code>包下。我們首先建立配置檔案在$APP_NAMEtoolsproguardproguard.conf(單獨抽到配置檔案裡,比寫到pom.xml裡更易讀),目錄結構大緻如下:
配置檔案<code>proguard.conf</code>内容如下:
然後在<code>$APP_NAME/pom.xml</code>中加入對<code>proguard-maven-plugin</code>的定義,避免每個module裡都把公共的代碼寫一遍:
同時在每一個module的<code>pom.xml</code>檔案裡,加入對<code>proguard-maven-plugin</code>的引用:
配置檔案、pom.xml檔案配完,後續開發、打包、上釋出系統就和普通的應用沒有任何差別了,maven打包完的<code>$filename.jar</code>所在目錄下有一個同名的<code>$filename</code>.jar.original包是未經混淆的包。
根據前一節中的配置進行混淆,可以看到源檔案行号已經無法還原,普通成員變量、本地變量的變量名已經替換成無意義名字,代碼結構有很細微的變化不影響結果。經過混淆和優化後,比原始的class檔案小了大緻23%。
對于不被其他應用代碼依賴的應用和需要釋出為二方包被别的應用依賴的應用,配置可能不同。二方包裡的類名、方法名不可混淆,同時可以通過混淆阻止其他應用通過反射來進行不安全的調用,當然對公共資料結構裡的方法不可混淆。對于直接釋出到伺服器上最終使用的應用,類名、變量名,甚至配置檔案都可以進行混淆,對于需要被反射的一些類,方法名甚至類名不能被混淆,如裝配時By name和By Type就有很大差別。
比如JavaBean混淆後,類成員變量的名稱可以變掉,方法名不變。這時候如果成員變量有注解類似于<code>@JsonIgnore</code>、<code>@JSONField(serialize=XX)</code>可能會失效,正确的應該把這些注解寫到Setter方法上。
混淆可以優化代碼,去除位元組碼中關聯的行号資訊,這時候如果出錯,日志會相對難調試。這個是雙刃劍,要麼接受混淆,要麼通過控制參數保留行号資訊。