Androidアプリをリリースする際に、プログラムの難読化を設定しなかった場合、アプリファイルをデコンパイルすることで、容易にコードの解析ができてしまいます。
【リスク】
・リリースしたアプリのソースコードが筒抜けになる。
・悪意のある第三者によるセキュリティーホールの解析が簡単になる。
【対策】
-
リリースビルド時、ProGuardを有効に
まず、リリースビルド時にProGuardを有効にします。
これでProGuardが次の2点をやってくれます。- クラス名、メソッド名、変数名などを短いランダム文字列にしてコードを読みにくくします → 難読化
- 実際は使われていないコードを削除します → APKファイルのサイズ削減
-
難読化の対象外設定
『全てのコードを難読化したから万々歳!』
であればとてもラクなのですが、
残念ながら気をつけなければならないことがあります。下記の実装をしているクラスは難読化から除外する設定(-keep)
をする必要があります。①リフレクションを使用しているクラス
リフレクションとはクラス名やメソッド名を呼び出す方法のことで、
例えば以下のようなClassクラスやConstructorクラスを指します。Class<Xxxx> sampleClass = Class.forName("com.example.Sample"); Constructor<Yyyy> constructor = sampleClass.getConstructor(int.class);
XxxxやYyyyの型が難読化で名称が変わってしまいエラーとなるので、
このクラスは難読化から除外する設定をしましょう。
(設定方法は後述します)リフレクションをクラス内で使用しているかを判断するには
importの中に「java.lang.reflect」が存在するかを
確認するのが最も単純な方法です。②外部ライブラリを使用しているクラス
外部ライブラリの内部で①のリフレクションや
後述の実装がされている場合はエラーになってしまうので、
ライブラリも難読化から除外する設定(-keep)をしましょう。Readmeに除外設定用の記述例が用意されていることが多いので
自己流で設定する前にまずは導入しているライブラリのReadmeを読んでみましょう。③値オブジェクト(Value Object)をjsonで送信しているクラス
値オブジェクトのフィールド名をjsonで送信する際に、
フィールド名が難読化で変わってしまい、
サーバーがレスポンスを認識できなくなってしまいます。
値オブジェクトをjsonで送信しているクラスを難読化から除外しましょう。④レイアウトファイルでバインディングしているクラス
Data Bindingを使用していて、
レイアウトファイルにフィールド名などを指定する場合、
フィールド名が難読化で変わってしまうので、
該当部分の機能や表示を損なってしまいます。
レイアウトファイルにフィールド名などを指定していたら
難読化の除外設定を行いましょう。⑤Parcelable Serializable Enumを実装・継承しているクラス
シリアライズされたEnumの値をファイル等に保存して永続化を行っている場合、
クラスの追加や削除で難読化後の名称が変わる可能性があり
デシリアライズ時のエラーに繋がるので難読化の除外設定を行いましょう。 -
難読化の設定方法
proguard-rules.proファイルに難読化から除外する設定を書きます。
細かい設定方法は沢山ありますが、ここでは-keep -keepclassmembers -keepclasseswithmembers -dontwarn
に絞って解説していきます。
-keep
-keepを指定すると、リネームを行わず、
不使用のコードの削除もしません。
<-keepの指定例>
クラス名、フィールド名、メソッド名を難読化から除外
(例はGoogleモバイルサービス)-keep class com.google.android.gms.**{ *;}
gms.の後の「**」はgms以下のパッケージ全てのことで、
{}内の「*;」はフィールド、メソッド全てを指します。-keepclassmembers
-keepclassmembersを指定すると、
フィールド名のみ難読化から除外され、クラス名は難読化されます。 <-keepclassmembersの指定例>
特定のアノテーションがついた
フィールド名のみを難読化から除外-keepclassmembers class *{@com.google.api.client.util.Key <fields>;}
「class *」の部分は全てのクラスを意味します。
「fields」は全てのフィールドを意味します。 この例ではKeyというアノテーションがついた
フィールド名を難読化から除外しています。-keepclasseswithmembers
-keepclasseswithmembersを指定すると、
フィールド名が存在する場合はクラス名とフィールド名を難読化から除外します。 <-keepclasseswithmembersの指定例>
publicなコンストラクタの引数がandroid.content.Context, android.util.AttributeSet, int
である場合、難読化から除外します。
-keepclasseswithmembers class *{public <init>(android.content.Context, android.util.AttributeSet, int);}
initは全てのコンストラクタを意味します。
指定の引数を持つコンストラクタ全てを難読化から除外します。
この3つの設定の派生で後ろにnamesをつけた-keepnames -keepclassmembernames -keepclasseswithmembernames
とすると、クラスは難読化しますが不使用のコードは削除するので
apkファイルのサイズを抑えることができます。
ただ、ProGuardが不使用と判断し削除したコードの中に
参照される値があった場合はエラーになってしまうので気をつけてください。-dontwarn
実際のプログラムで参照しているのに実行時に使用されないライブラリがある場合に、
ProGuardが参照先のクラスが見つからないというWarningを出してビルドが中断されます。[proguard] Warning: org.apache.log4j.lf5.viewer.LogBrokerMonitor$32: can't find superclass or interface java.awt.event.ActionListener
<-dontwarnの指定例>
-dontwarn com.google.android.gms.**
書き方は-keepと同じです。
-
R8について
R8とはGoogleのAndroid開発チームが作ったAndroid専用のコード圧縮ツールです。
(ProGuardはGuardSquare社が開発したJavaコードのコード圧縮ツール)R8の方がProGuardより優れている点としては、
圧縮のためのビルド時間が短縮できることと、
Kotlinへのサポートが充実していることが挙げられます。Android Gradleプラグイン3.4.0以上から、
コンパイル時にProGuardのコード最適化が行われなくなりました。
代わりにR8コンパイラで「コード圧縮、リソース圧縮、難読化、最適化」を行います。この変更に対して特に追加する設定はありません。
(デフォルトでandroid.enableR8=true となるので、
gradle.propertiesにわざわざ追記しなくても問題ありません)
ですので、ProGuardからR8への移行は難しいものではなく、
既存のProGuardをそのままR8として使用するような感覚です。また、R8の「完全モード」で更なる最適化をしてアプリサイズを削減することもできます。
android.enableR8.fullMode=true
しかし、R8の完全モードはProGuardとの互換性がないため、
保持ルールを追加する対応が必要となる可能性が高いです。R8について知っておくべきことの要約は以上です。
詳細については公式ドキュメントをご覧ください。Android Developers: アプリの圧縮、難読化、最適化
ProGuardによる難読化はソースコードの漏洩、
悪意あるユーザーの解析から防衛する手段なので、
アプリのセキュリティを高めるには有効な手段と言えます。
ただ、注意して難読化除外の設定をしないと
アプリが機能しなくなってしまうので、
細心の注意を払って設定しましょう。
他にもProGuardの有償版である
DexGuardという難読化ツールもあります。
ProGuardとの主な違いとしては、
DexGuardは静的な文字列やリソースファイルも難読化することや
アプリ内へのセキュリティツールの組み込みが可能なことです。
DexGuardの書き方はProGuardと同じですが、
より多機能なのでProGuardにはない設定も可能です。
DexGuardが有償ツールということもあり、
より広く使用されているのはProGuardです。
ProGuardを知っていればAndroidにおける難読化の要点を押さえることができます。
Android Gradleプラグイン3.4.0以上の場合は、
ProGuardの設定がしてあればR8で圧縮、難読化、最適化をしてくれます。