はじめまして。Android(たまにiOS)エンジニアのほかりです。 最近はKotlinに興味津々です。 でも今回はKotlinの話はせず、Proguardの話をしようと思います。
Proguardとは
AndroidにおけるProguardとはアプリ(apkファイル)に難読化処理を施すツールのことです。 難読化されていないアプリはリバースエンジニアリングを行うとソースコードなどを見られてしまう恐れがあります。 Proguardをかけることによってソースコード上のクラス名やメソッド名などが何の意味も持たないようなアルファベットに置き換わります。 こうすることでソースコードを解読する難易度を高めAPKファイルのセキュリティ性を高めます。
Proguardを有効にすることで以下のようなメリットがあります。
- ソースコードの難読化
- パッケージサイズの軽量化
- パフォーマンスの向上?
さっそくProguardを有効にしていきましょう!!
Proguardの有効化
Proguardの設定方法について説明する前に前提として本記事ではAndroidStudioでAndroidアプリの開発をしているものとします。 app配下のbuild.gradleファイルに以下を記述します。
android { ... buildTypes { release { minifyEnabled true // デフォルトだとここがfalseなのでtrueにします。 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
上記の設定でbuildTypeがreleaseのときのみProguardが有効になります。 実際の設定はapp/proguard-rules.proに記述していくことになります。 次にapp/proguard-rules.proに記述する内容を見ていきましょう!
Proguardの設定
先ほどの工程でProguardを有効にしているのにも関わらずproguard-rules.proに何も記述しないとリリースビルド時に全てのコードに難読化をかけてしまいます。 この場合、単純な実装の場合は特に問題は起きないのですが、アプリ開発はそう単純なことで終わることもなく以下のようなことをしているととっても面倒なことになります。(そんなメソッドはねーよ!とか言ってきます。)
- AndroidManifest.xmlでのみ参照されるクラス
- JNIから呼ばれるクラス
- 動的に参照されるフィールドやメソッド
- ライブラリを導入している
というわけで設定をしていきます。基本的な文法は以下のようになります。
-keep class パッケージを含むファイル名(パッケージ内の全てを対象にする場合は**でOK)
と難読化をかけたくないものに対してkeepオプションを設定してあげると難読化の対象外となります。 他にも下記のようなオプションがあります。
オプション | 詳細 |
---|---|
optimizationpasses | 最適化の処理回数の指定。デフォルトは1回。複数回実施することで処理結果の向上が期待できます。apkファイルのサイズに影響します |
dontoptimize | 最適化を行わない |
dontusemixedcaseclassnames | 最適化処理で大文字と小文字を混ぜたクラス名に変更しない。Windows環境でこのオプションを指定しないと、ファイル名の大文字小文字の区別がされないため上書きされる可能性がある |
dontskipnonpubliclibraryclasses | 非publicなライブラリクラスをスキップしないようにする |
dontpreverify | 処理済みファイルの事前検証を行わないようにし、VMの読み取り速度を高速化する |
dontwarn | 警告を握りつぶす。警告の原因が明らかで無視してもよい場合は記述してよいが、それ以外は根本解決とならず危険 |
verbose | 処理中の情報を詳しく書き出す |
optimizations | 最適化対象 ・ !code/simplification/arithmetic → 算術命令に対してヒープホール最適化を行わない ・ !field/ → fieldで始まるすべてのフィルタを除外する。 ・ !class/merging/ → クラス階層においてクラスのマージを行わない。 |
keep | クラスとクラスメンバをリネーム、削除しない |
keepnames | クラスとクラスメンバをリネームしない |
keepclassmembers | クラスメンバをリネーム、削除しない |
keepclassmembernames | クラスメンバをリネームしない |
keepclasseswithmembers | クラスメンバが存在した場合のクラスとクラスメンバをリネーム、削除しない |
keepclasseswithmembernames | クラスメンバが存在した場合のクラスとクラスメンバをリネームしない |
アプリ開発をしている上でProguardの設定を必要とされる多くのケースはライブラリを導入したときかと思いますが、ちゃんとREADMEが書かれているライブラリであればProguardの設定も記載されているのでそのままコピペで大半はうまくいきます(とはいえリリースビルドしたアプリの動作確認を推奨します)。
Proguardの設定が書かれていない場合は以下の2通りが考えられます。
- Proguardの設定を必要としない
- Proguardの設定が実は必要だけど書いていない
前者は全く問題ありません。後者は本当に最悪です。同じ問題に遭遇しすでにStackOverflowとかで解決策があることを祈ってググるか、コードを読むなり、実際に動かしてみるなりして設定が必要な部分を調査する必要があります。
注意すべきこと
ライブラリに関しては先ほど述べたようにREADMEに記載されているものをコピーすれば良いです。
注意すべき点は自分(またはチームの誰か)が書いたコードです。
これはググっても出てくるわけでもないので自分で設定するしかありません。
ほとんどの場合はリネームをしても問題はないのですが、リフレクションなどをしている場合はそうではありません。
getDeclaredField(String)
では引数にフィールド名を指定してアクセスするのでProguardによってリネームされると動きません。
そして厄介なのはこの場合コンパイル時エラーにはならずその機能が動かないだけということになります。(try/catchするので)
幸いAndroidのソースコードはオープンソースなので難読化をかけなくても問題と思うのでリフレクションを使う場合は対象のクラスを除外する設定をしましょう!
おまけ
FirebaseやCrashlyticsなどクラッシュレポートツールについてです。
これ自体は実際にユーザーが発生したエラーのスタックトーレスとかが見れてとても便利です。 そう。難読化されていなければね。
Proguardが有効になっているアプリがクラッシュし、クラッシュレポートが送信されるとそのスタックトレースももちろん難読化されています。
省略 at [パッケージ名].f.a.b(ProGuard:577) 省略
こんなものをみて修正するなんて現実的ではありません。 多くのクラッシュレポートツールでは難読化されたスタックトレースを復元してくれる機能が備わっています。
アプリをビルドした時に自動生成される/proguard/mapping.txt
というものをアップロードすると
アップロードした以降のスタックトレースは復元され表示されます。
Proguardを有効にしたアプリを運用していく場合はアップデートをするたびにmappingファイルも一緒にアップロードし直す必要があります。
ですが人間が手作業で行う作業でもあるため、うっかりアップロードし忘れるなんてこともなくありません。 アップロードし忘れるとやっぱり、
省略 at [パッケージ名].f.a.b(ProGuard:577) 省略
こんな感じのスタックトレースになるわけです。 しかし、ビルドした時のmappingファイルを持っている場合はローカル環境で復元することも可能なのです。
AndroidSDK/tools/proguard/bin/retrace.sh
が復元をしてくれます。
使い方は以下のコマンドをターミナルで実行するだけです。
sh [retrace.shまでのフルパス] -verbose mapping.txt [復元対象のStackTraceをコピペしたテキストファイル]
上記を実行するとターミナル上に復元されたスタックトレースが表示されます。
注意事項として使用するmappingファイルはクラッシュレポートが送られた時に実行されたアプリをビルドした時のものにしてください。 mappingファイルは基本的にビルドするたびに変更される(可能性がある)ので全く同じコードでビルドして生成されたmappingファイルを使ってもうまく復元されないことがあります。
さいごに
AndroidのProguard関連で悩んでいる人のお役に立てたら嬉しい限りでございます!