ProGuard/R8: Androidアプリの難読化と保護

著者: IT Sectr 公開日: 2026-02-14 読了時間: 8 分

ProGuardR8は、Androidアプリケーション向けの難読化、縮小化、最適化ツールです。2002年に作成されたProGuardは、長い間Javaコード保護のデファクトスタンダードでした。R8はその後継であり、Googleによって開発され、AGP 3.4以降Android Gradle Pluginに組み込まれています。両方のツールはAPKサイズを削減し、デッドコードを除去し、リバースエンジニアリングを困難にします。Android Developersによると、R8は同等の難読化品質でProGuardの2~3倍高速にビルドを実行します。

重要ポイント

  • ProGuard — Javaバイトコードの難読化・最適化ツール、2000年代からAndroidの標準
  • R8 — GoogleによるProGuardの後継、AGPに組み込み、1パスで難読化・縮小化・最適化を実行
  • 難読化はクラスやメソッドを短い名前にリネームし、アプリのリバースエンジニアリングを困難に
  • 縮小化は未使用のクラス、メソッド、フィールドを削除し、最終APK/AABサイズを削減
  • ProGuardルール(.proファイル)は、コードのどの部分を保持、難読化、削除するかを制御

ProGuardとは?

ProGuardは、Javaバイトコードの難読化、縮小化、最適化、事前検証のためのオープンソースツール(Apache 2.0)です。2002年にEric LafargeによってSourceForgeプロジェクトの一部として開発されました。ProGuardはコンパイル済みJavaクラス(.class)またはJARアーカイブを入力として受け取り、同じ形式で処理済みのクラスを出力しますが、サイズが小さく、要素がリネームされています。

長い間、ProGuardはAndroidアプリケーションをリバースエンジニアリングから保護する唯一の標準でした。GoogleはAndroid SDKでの使用を公式に推奨し、SDKツール内のproguard-android-optimize.txtファイルにデフォルト設定を提供していました。ProGuardは独立したツールとして動作し、Javaコードのバイトコードへのコンパイル後、DEXへのパッケージ化前に実行されていました。

ProGuardのアーキテクチャ

ProGuardは4つの連続フェーズで構成されています:shrink(未使用クラスの削除)、optimize(バイトコード最適化 — インライン化、デッドコード削除)、obfuscate(クラス、メソッド、フィールドの短い名前へのリネーム)、preverify(JVM互換性チェック)。各フェーズは設定ファイルからの個別のルールによって制御されます。

難読化段階で、ProGuardはマッピングファイル(mapping.txt)を生成し、元の名前を難読化後の名前にマッピングします。このファイルは、retraceユーティリティを使用してリリースビルドからクラッシュログをデコードするために重要です。マッピングファイルがないと、スタックトレースはa()、b()、c()という文字の羅列になり、元のコンテキストを復元する方法がありません。

ProGuardフェーズ目的結果
Shrinkコールグラフ分析とデッドコード削除APK内のクラス数減少
Optimizeメソッドのインライン化、未使用パラメータの削除コード実行の高速化
Obfuscateクラス、フィールド、メソッドのリネームリバースエンジニアリング対策
PreverifyJVM用StackMap属性の追加Java 6+互換性

R8とは?

R8はGoogleの次世代難読化・縮小化ツールで、Android Studio 3.3(2018年11月)で初めて導入され、AGP 3.4(2019年8月)で標準となりました。ProGuardとは異なり、R8はJavaバイトコードをDEX形式に変換するD8/R8コンパイラの一部です。R8はすべてのフェーズ(難読化、縮小化、最適化)を1パスで実行し、ツール間で中間ファイルを渡す必要がありません。

GoogleはR8を2つの目標で開発しました:ビルドの高速化(ProGuardは外部ツールとして動作)と、最新のAndroidスタック(Desugar、Core Library Desugaring、D8)とのシームレスな統合です。R8はKotlinとJavaで書かれており、AOSP(Android Open Source Project)のR8/Desugarリポジトリの一部です。

R8の重要な利点は、ProGuardルールとの完全な下位互換性です。既存の.proファイルは変更なしで動作します。R8は-whyareyoukeeping、-printconfiguration、-printmappingなどのProGuard固有のディレクティブもサポートしています。つまり、ProGuardからR8への移行は透過的で、AGPを更新するだけです。

kotlin
// build.gradle.kts — minifyEnabledによるR8の有効化
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true

            proguardFiles(
                // Android SDKの基本設定
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // カスタムプロジェクトルール
                "proguard-rules.pro"
            )
        }
    }
}

コードは標準的なリリースビルド設定を示しています。フラグisMinifyEnabled = trueは難読化と最適化のためにR8を有効にします。isShrinkResources = trueは未使用リソースを追加で削除します。getDefaultProguardFileはSDKからデフォルトルールをロードし、proguard-rules.proにはプロジェクト固有の設定が含まれます。

Androidのコード難読化

難読化とは、ソースコードを人間が分析しにくい形式に変換するプロセスですが、完全な機能は維持されます。Androidのコンテキストでは、難読化とはクラス、メソッド、フィールドを短く意味のない名前にリネームすることです:com.example.app.auth.LoginManagera.a.aに、メソッドauthenticateUseraに、フィールドuserTokenbになります。

なぜ難読化が必要か

Android APKファイルは、任意のアーカイバ(ZIP、7z、WinRAR)で開くことができるアーカイブです。難読化がない場合、攻撃者はアプリケーションの完全なマップ(パッケージ名、クラス、メソッド、フィールド)を入手できます。jadxBytecode Viewerのようなツールは、DEXファイルからほぼ元のJavaコードを数秒で復元できます。難読化はコードを無敵にするわけではありませんが、参入障壁を大幅に高めます:意味のある名前の代わりに、読者はa()、b()、c()を見ることになります。

典型的な難読化の目標:商業ロジック(アルゴリズム、計算式)の保護、APIキーとトークンの窃取防止、リフレクションによるクラス置換の防止、APKのパッチ適用と改変(リパッケージ攻撃)の防止。実際には、70%のタスクはリネームだけで解決されます — これがProGuard/R8が使用される理由です。

ProGuardルールの例

以下は、Retrofit、Gson、Parcelableを使用するAndroidプロジェクトの典型的なproguard-rules.proファイルです。-keepルールは、リフレクションを通じたライブラリ操作に必要なクラスとメソッドを保持します。これらのルールがないと、R8はライブラリが文字列名でアクセスするクラスを削除またはリネームします。

pro
# =====================
# Retrofit — インターフェースの保持
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions

# =====================
# Gson — JSONシリアライゼーション
# =====================
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
    <fields>;
}

# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

# =====================
# Logging — リリースからのログ削除
# =====================
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
    public static int w(...);
    public static int e(...);
}

# =====================
# Kotlinデータクラス — コンストラクタの保持
# =====================
-keepclassmembers class * {
    @kotlin.Metadata <fields>;
}

# =====================
# Activity — エントリポイント
# =====================
-keep class * extends android.app.Activity {
    @android.annotation.SuppressLint <methods>;
}

.proファイルの各ディレクティブは特定のタスクを解決します。-keepはクラス全体の削除またはリネームを防ぎます。-keepclassmembersはクラスメンバー(フィールドとメソッド)のみを保護しますが、クラス自体が未使用の場合は削除を許可します。-assumenosideeffectsはメソッド呼び出しに副作用がないことをR8に伝え、安全に削除できるようにします。-keepattributesディレクティブはバイトコード内のメタデータ(アノテーション、シグネチャ、例外)を保持します。

Retrofitのルール-keep,allowobfuscation,allowshrinkingは、R8がインターフェースをリネームすることを許可しますが、削除は許可しません。これは、Retrofitが動的プロキシ(java.lang.reflect.Proxy)を介してインターフェースにアクセスするため、削除すると実行時にClassNotFoundExceptionが発生するからです。同様に、Gsonは@SerializedNameでアノテーションされたフィールドにアクセスするためにリフレクションを使用します — -keepclassmembersがないと、フィールドは未使用として削除されます。

縮小化とShrinkResources

縮小化(shrinking)は、最終ビルドから未使用のコードとリソースを削除するプロセスです。ProGuardとR8はエントリポイント(Activity、Service、BroadcastReceiver)からコールグラフを分析し、コールチェーンを通じて到達できないクラスとメソッドを削除します。ShrinkResourcesはres/(layout、drawable、string、color)から未使用のリソースを削除する追加ステージです。

縮小化は、ライブラリを使用する大規模プロジェクトで最大のメリットをもたらします。典型的なシナリオ:プロジェクトが接続されたライブラリ(例:Google Play Services)のコードの10%しか使用していない場合、縮小化がないとライブラリコード全体がAPKに入ります。縮小化により、R8はライブラリコードの70~90%を削除し、実際に使用されているクラスとメソッドだけを残します。これはAPKサイズ、ロード時間、メモリ消費に直接影響します。

ShrinkResourcesの動作

ShrinkResourcesメカニズムはコード縮小化と連携して動作します。R8がどのクラスが使用されているかを判断した後、リソース縮小化はコードからのリソース参照を分析します:R.layout.mainR.drawable.icongetString(R.string.title)。直接的または間接的な参照がないすべてのリソースは、最終的なAPKまたはAABから削除されます。これはリソースファイルresources.arscとres/フォルダを使用して行われます。

重要なニュアンス:リソースはgetIdentifier()またはResources.getResourceName()を介して文字列名でアクセスでき、Rクラスをバイパスします。そのような場合、R8は直接リンクを認識できず、実際に使用されているリソースを削除する可能性があります。そのようなリソースを保護するために、ディレクティブ-keep class **.R$* { *; }があります — これはRクラスのすべての識別子を保持します。

xml
<!-- 例: getIdentifier()を介してのみ使用されるリソース -->
<string name="dynamic_title_welcome">ようこそ</string>
<string name="dynamic_title_share">共有</string>

<!-- 文字列でアクセスするKotlinコード -->
<!-- val title = getString(resources.getIdentifier( -->
<!--     \"dynamic_title_${type}\", \"string\", packageName)) -->

この場合、R8はRクラス内のdynamic_title_welcomeへの静的参照を認識しません。なぜなら、アクセスが動的名前を持つgetIdentifierを介しているからです。そのようなリソースを保持するには、proguard-rules.proにディレクティブ-keepclassmembers class **.R$string { *; }を追加します — これにより、すべてのR$stringクラスからのフィールド削除が防止されます。

ディレクティブ目的
-keepクラスとそのすべてのメンバーを保持-keep class com.example.api.** { *; }
-keepclassmembersクラスメンバーのみを保持-keepclassmembers class * { @SerializedName <fields>; }
-keepattributesバイトコードメタデータを保持-keepattributes *Annotation*, Signature
-assumenosideeffects副作用のない呼び出しを削除-assumenosideeffects class Log { d(...); }
-dontwarn警告を抑制-dontwarn com.example.legacy.**

R8 vs ProGuard: 主な違い

R8がProGuardの後継であるにもかかわらず、アーキテクチャ、パフォーマンス、動作においてツール間に根本的な違いがあります。GoogleはAGP 7.0以降、Android Gradle PluginでのProGuardサポートを正式に終了しましたが、ProGuardはR8では利用できない特定の最適化動作を必要とするプロジェクトで引き続き使用されています。

比較表

特性ProGuardR8
開発者GuardSquare(Eric Lafarge)Google
リリース年2002年2018年(2019年に安定化)
アーキテクチャ4つの個別フェーズ(shrink → optimize → obfuscate → preverify)単一パス:shrink + optimize + obfuscateを同時実行
AGP統合外部ツール、javac後に実行D8 DEXコンパイラに組み込み
ビルド速度2~3倍遅い単一パスとネイティブ統合により高速
Kotlinサポート限定的(inline、lambdas、coroutinesに問題)完全:coroutines、inline関数、data class
マッピングファイルmapping.txt(retraceと互換)mapping.txt(同じ形式)
最適化カスタマイズ60以上のオプション -optimizationpasses、-optimizations限定的:ほとんどの最適化はデフォルトで有効
サポート状況R8に置き換え(AGP 7.0+では不使用)活発に開発中、AOSPの一部

R8がビルドを壊す可能性がある場合

R8はProGuardよりも積極的にデッドコードと判断したコードを削除します。これにより、デバッグビルドは動作するがリリースビルドがClassNotFoundExceptionやNoSuchMethodExceptionでクラッシュする状況が発生します。典型的なケース:クラス名でリフレクションを使用するライブラリ(Gson、Moshi、Retrofit、Room、Dagger);ServiceLoaderやjava.util.ServiceLoaderの呼び出し;動的プロキシ(java.lang.reflect.Proxy);ネイティブメソッド(JNI)。解決策は、リフレクションを介して呼び出されるすべてのクラスに-keepを追加することです。

pro
# 典型的なリフレクションの問題 — R8が静的リンケージを認識しない

# Room — DAOとマイグレーションの保持
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }

# Dagger / Hilt — コンポーネントの保持
-keep class * extends dagger.hilt.android.components.** { *; }

# JNI — ネイティブメソッドをリネームしない
-keepclasseswithmembernames class * {
    native <methods>;
}

# Data Binding — Bindingクラスの保持
-keep class *.databinding.** { *; }

ルールを追加してもビルドがクラッシュする場合は、proguard-rules.proでフラグ-printconfiguration full-config.txtを使用します。R8は、どのルールが適用され、どのクラスが保持されているかを示す完全な設定ファイルを生成します。ディレクティブ-whyareyoukeeping class com.example.MyClassも便利です — R8が特定のクラスを保持することを決定した理由を出力します。

ProGuardルールの設定

ProGuardルールの適切な設定は、実行時バグのない安定した難読化の鍵です。以下は、新規プロジェクトまたは難読化がエラーを引き起こすプロジェクトのためのステップバイステップのセットアッププロセスです。

ステップ1:基本設定

まず、標準のAndroid SDKファイルproguard-android-optimize.txtを含めます。これには、基本的なAndroidコンポーネント(Activity、Service、BroadcastReceiver、ContentProvider、View、Fragment)のルールが含まれています。このファイルはSDKフォルダにあります:$ANDROID_HOME/tools/proguard/proguard-android-optimize.txt。AGPを使用している場合、getDefaultProguardFileが自動的にロードします。

ステップ2:ライブラリ

一般的なライブラリにはそれぞれ推奨されるProGuardルールがあります。Retrofit、OkHttp、Glide、Fresco、Coil、Room、Dagger/Hilt、Kotlin Coroutines — すべてに特定の-keepルールが必要です。通常、ルールはAARライブラリに含まれており、コンシューマーガードルールを介して自動的にリンクされます。ライブラリがAAR内にproguard.txtファイルを提供しているか確認してください — これはルールがすでに考慮されていることを示します。

ステップ3:リリースビルドのテスト

公開前に、実際のデバイスまたはエミュレータでリリースビルドをテストしてください。難読化の問題は実行時にのみ現れます。確認事項:認証(ログイン/登録)、ネットワークからのデータ読み込み、画面間のナビゲーション、カメラとギャラリー、プッシュ通知、Deeplinks、WebView。リリースビルドの各クラッシュは、マッピングファイルを使用してretraceでデコードし、不足している-keepルールを追加する必要があります。

ステップ4:マッピングファイルとCI

マッピングファイルはbuild/outputs/mapping/release/mapping.txtに生成されます。このファイルは保存が必須です:これがないとGoogle Play Consoleからのクラッシュログをデコードできません。mapping.txtをバージョン管理システムに含めるか、CIアーティファクトにアップロードしてください。Google Play Consoleはuploading mapping.txtを有効にしてAABをアップロードすると自動的にマッピングファイルを受け入れます。

以下は、ルールの各グループのコメント付きの、proguard-rules.proファイルでの完全な難読化設定ワークフローです。

pro
# ===========================================
# proguard-rules.pro — 完全な例
# ===========================================

# --- 一般設定 ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify

# --- Androidコンポーネント ---
-keep public class * extends android.app.Activity
-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.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View

# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions

# --- Gson / Moshi ---
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }

# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}

# --- シリアライゼーション ---
-keepclassmembers class * implements java.io.Serializable {
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# --- R8のみ: 強制保持 ---
# (ProGuardはこのディレクティブを無視)
-keep,allowobfuscation class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

設定後、ビルドを実行します:./gradlew assembleReleasebuild/outputs/mapping/release/にファイルが表示されることを確認します:mapping.txt(元の名前から難読化後の名前へのマッピング)、seeds.txt(-keepルールで保持されたクラス)、usage.txt(縮小化中に削除されたクラス)。難読化後のAPKサイズは、接続されたライブラリの数に応じて20~50%減少するはずです。

よくある質問

R8とProGuardの違いは何ですか?

R8はProGuardの後継で、Googleが開発しました。R8は難読化、縮小化、最適化を1パスで実行し、ProGuardの2~3倍高速で、Android Gradle Pluginに直接統合されています。ProGuardは4つの個別フェーズを使用し、外部実行が必要です。AGP 7.0以降、ProGuardは使用されず、デフォルトでR8が動作します。

R8を使用する場合、ProGuardルールを記述する必要がありますか?

はい、R8は同じProGuardルール(.proファイル)を使用します。ディレクティブ-keep、-keepclassmembers、-keepattributes、-assumenosideeffectsは同じように動作します。基本ルールはAndroid SDKのproguard-android-optimize.txtに含まれ、ライブラリ固有のルール(Retrofit、Room、Gson)はプロジェクトのproguard-rules.proに追加します。これらのルールがないと、R8はリフレクションを介して動作するライブラリに必要なクラスを削除する可能性があります。

AndroidプロジェクトでR8を有効にするには?

R8はAGP 3.4以降、Android Gradle Pluginでデフォルトで有効です。縮小化を有効にするには、build.gradle.ktsのrelease buildTypeブロックでisMinifyEnabled = trueを設定します。追加フラグisShrinkResources = trueは未使用リソースの削除を有効にします。gradle.propertiesでandroid.enableR8=falseによりR8を強制的に無効にできますが、推奨されません — R8の方が高速で安定しています。

Androidのコード難読化とは何ですか?

難読化 — クラス、メソッド、フィールドを短い意味のない名前(a、b、c)にリネームすること。クラスcom.example.app.auth.LoginManagerはa.a.aに、メソッドauthenticateUserはaになります。これによりアプリケーションのリバースエンジニアリングが困難になりますが、実行ロジックには影響しません。ProGuardとR8は、-keepルールで保護されていない要素のみをリネームします。マッピングファイルは、クラッシュログをデコードするために元の名前と難読化された名前の対応を保持します。

難読化されたアプリケーションのクラッシュログをデバッグするには?

スタックトレースをデコードするには、retraceユーティリティ(ProGuard/R8 SDKの一部)を使用します。コマンド:retrace mapping.txt crash-stacktrace.txt。マッピングファイルはbuild/outputs/mapping/release/mapping.txtにあります。Google Play ConsoleもAAB公開時にmapping.txtのアップロードをサポートしており、クラッシュログはコンソールで自動的にデコードされます。マッピングファイルがないと、スタックトレースにはa.b.c()のような難読化された名前のみが含まれ、デバッグには役立ちません。

まとめ

  • ProGuard — 4つの連続フェーズからなるクラシックなJavaバイトコード難読化・最適化ツール
  • R8 — Googleのモダンな後継、AGPに組み込み、すべてのフェーズを1パスで2~3倍のパフォーマンスで実行
  • 難読化はクラス、メソッド、フィールドを短い名前にリネームし、リバースエンジニアリングを困難にしアプリの商業ロジックを保護
  • 縮小化は未使用のコードとリソースを削除し、典型的なプロジェクトでAPKサイズを20~50%削減
  • ProGuardルール(.proファイル)は難読化の動作を制御 — ディレクティブ-keep、-keepclassmembers、-assumenosideeffectsはどの要素を保持、削除、リネームするかを指定
  • マッピングファイル(mapping.txt)はretraceを介してリリースビルドからクラッシュログをデコードするための非常に重要なビルドアーティファクト
  • テストは実機でのリリースビルドが必須 — 難読化の問題は実行時にのみ現れ、不足している-keepルールの追加が必要

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください