بهینه کردن و محافظت از سورس برنامه با ProGuard/R8

محافظت از سورس برنامه اندرویدی در برابر دیکد شدن و بهینه کردن آن با ProGuard/R8
یکی از مشکلاتی که اپلیکیشن‌های اندرویدی و صاحبان آن را تهدید می‌کند، سرقت کدهای سورس برنامه توسط سایر افراد است که اصطلاحا دیکد (Decode) نامیده می‌شود. روش مهندسی معکوس باعث می‌شود هر شخص دیگری بتواند کدهای برنامه شما را مشاهده کرده و از آنها استفاده کند.
در این آموزش به معرفی روش‌ها و ابزار لازم برای محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن آن توسط فعال کردن ProGuard (پروگارد) و البته جایگزین جدید آن در اندروید استودیو با نام R8 می‌پردازیم. همچنین در مورد بهینه سازی کدهای پروژه اندرویدی و کاهش حجم برنامه که از دیگر وظایف R8 است نیز صحبت خواهیم کرد.

محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد

به نام خدا. قطعا عبارت “مهندسی معکوس” را بارها شنیده و خوانده‌اید. مهندسی معکوس یا Reverse Engineering در حوزه صنعت به اینصورت است که یک سخت افزار که قبلا توسط یک شخص/گروه/نهاد و یا دولت ساخته شده توسط شخص یا گروهی دیگر بررسی شده و نمونه مشابه آن با هزینه و صرف وقت کمتر تولید می‌شود.
اما این اتفاق صرفا محدود به ابزار صنعتی و نظامی نیست و در همه حوزه‌ها شاهد وقوع آن هستیم. از جمله در زمینه IT و نرم افزارهایی که برای پلتفرم‌ها و سیستم عامل‌های مختلف توسط گروه‌های متعددی منتشر شده‌اند.
قبلا در مبحث آشنایی با سیستم عامل اندروید گفتیم که برنامه‌های اندرویدی هم مانند هر پلتفرم دیگری برای آنکه قابلیت اجرا روی دستگاه را داشته باشد ابتدا باید کامپایل شوند. زمانی که ما از پروژه خود خروجی APK یا AAB در اندروید استودیو می‌گیریم عملیات کامپایل شدن سورس برنامه انجام می‌شود.
پس از Compile شدن پروژه اندرویدی، ساختار کدها دگرگون شده و کدها مانند قبل خوانا نیست اما نه به این معنی که قابل برگشت نباشد! برنامه‌های جاوا را می‌توان به سادگی دیکامپایل (Decompile) کرد. کافیست عبارت Java decompiler را در گوگل جستجو کنید.
دیکامپایل کردن برنامه‌های اندروید هم به همین سادگی انجام می‌شود. ابزار معروفی با نام JADX که به صورت رایگان و منبع باز در مخزن گیت هاب در دسترس همگان قرار دارد. حتی شخصی که بخواهد اپلیکیشن شما را دیکامپایل کند نیازی به راه اندازی JADX روی رایانه خود را نیز نداشته و در وب سایتی مانند javadecompilers.com می‌تواند فایل APK اپ را بارگزاری کرده و نسخه دیکد شده را در ظرف چند ثانیه تحویل بگیرد.
البته که نسخه دیکد شده‌ی دریافتی از JADX آماده ایمپورت در محیط توسعه اندروید استودیو نیست اما بهرحال کلاس‌ها و کدهای جاوا قابل دسترسی هستند و استفاده یا بهتر است بگوییم سوء استفاده از آنها را تسهیل می‌کند.
ضمن اینکه لازم به ذکر است با توجه به ماهیت زبان جاوا، ما هیچگاه نمی‌توانیم جلوی دیکد شدن کدها و کلاس‌های جاوا را بگیریم و صرفا باید سعی کنیم خوانایی و درک کدها پس از دیکد شدن برای افراد سخت تر شود. کدهای جاوا پس از دیکامپایل، به byte code ها تبدیل می‌شود نه کدهای native بنابراین دیکد کردن آن به سادگی امکانپذیر است.
در مواردی مانند پرداخت درون برنامه‌ای که اپلیکیشن کد یکتایی را از مارکت اندرویدی برای صحت سنجی سابقه خرید کاربر دریافت می‌کند، هکر می‌تواند این کد را غیر فعال کرده و بدون پرداخت هزینه به شما از امکانات برنامه استفاده کند.
بدترین اتفاق می‌تواند این باشد که شخصی برنامه شما را دیکامپایل کرده و با صرف هزینه و زمانی بسیار کمتر از آنچه شما بهایش را پرداخته‌اید، برنامه‌ای مشابه اپلیکیشن شما ساخته و با نام خودش در مارکت‌ها منتشر کند و حتی با نمایش تبلیغات و یا پرداخت درون برنامه‌ای به کسب درآمد بپردازد!
برای محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن کدهای جاوا و به عبارتی جلوگیری از دیکامپایل شدن اپ، روش‌ها و ابزار متعددی وجود دارد. برخی از ابزار رایگان و برخی دیگر نیاز به پرداخت هزینه لایسنس دارند. در این مبحث تنها به بررسی روش‌های رایج و ابزار رایگان می‌پردازیم و سایر موارد را صرفا به اشاره‌ای اکتفا خواهیم کرد.
Obfuscation به معنی مبهم سازی، اصلی ترین کاری است که به جهت محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن کدهای جاوا انجام می‌شود. در ادامه مبحث به طور مفصل به جزئیات می‌پردازیم.

پروگارد (ProGuard) چیست؟

اگر به محتویات فایل build.gradle (Module:App) پروژه‌های اندرویدی در اندروید استودیو دقت کرده باشید یک بلاک به صورت زیر به صورت پیش فرض وجود دارد:

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
نکته: کدهایی که درون بلاک buildTypes قرار می‌گیرند تنها هنگام بیلد شدن پروژه اجرا می‌شوند. درون این بلاک یک بلاک دیگر با نام release وجود دارد که کدهای مربوط به پروگارد داخل آن تعریف شده. این کدها هنگامی اجرا خواهند شد که بخواهیم خروجی نسخه release از پروژه خود بگیریم. همچنین اگر بخواهیم کدهای پروگارد یا هر کد موردنظر دیگری را هنگام debug پروژه اجرا کنیم باید بجای بلاکی با نام release در بلاکی با نام debug درون بلاک buildTypes تعریف شود.

فعال کردن ProGuard در اندروید استودیو بسیار ساده است. پروگارد در بیلد سیستم Gradle قرار داده شده و نیاز به نصب جداگانه ندارد. در حالت پیش فرض برای minifyEnabled مقدار false تعریف شده. این آیتم برای تعیین فعال یا غیر فعال بودن Proguard هنگام کامپایل شدن پروژه استفاده می‌شود. ترجمه تحت الفظی آن می‌شود “کوچک کردن فعال است”. چنانچه مقدار را به true تغییر دهیم، پروگارد فعال شده و هنگام گرفتن خروجی پروژه (release یا debug) وظایف خودش را انجام خواهد داد.
در خط دوم، فایل تنظیمات و قوانین پیش فرض پروگارد توسط proguardFiles تعیین شده است.

ProGuard چه وظایفی را بر عهده دارد؟

در صورت فعال کردن پروگارد در پروژه اندرویدی در اندروید استودیو، سه عمل در هنگام کامپایل شدن انجام می‌شود که به شرح زیر می‌باشد:
مبهم سازی نام‌ها یا Name obfuscation: تغییر نام کلاس‌ها، فیلدها و متدها با نام‌های کوتاه و عموما یک کاراکتری. برای مثال ممکن است نام کلاس Users به a تغییر پیدا کند که علاوه بر کاهش تعداد کاراکترهای نام کلاس هنگام فراخوانی و به دنبال آن کاهش حجم نهایی کدها، خوانایی، درک و تشخیص کدها را تا حدود زیادی مشکل می‌کند.
Shrinking یا Tree shaking: این قابلیت باعث می‌شود تا کلاس‌ها، متدها، فیلدها، ویژگی‌ها (attributes) و کتابخانه‌های بلا استفاده درون پروژه هنگام کامپایل حذف شوند که باز هم کاهش حجم نهایی اپ را بدنبال خواهد داشت. بنابراین این قابلیت به بهینه شدن و افزایش سرعت اجرای برنامه روی دستگاه کاربر کمک می‌کند.
بهینه سازی کد یا Code optimization: در هر دو مورد قبل عمل بهینه کردن کدها انجام می‌شد اما هنوز هم برای کمتر شدن حجم کدها جای کار هست. وظیفه دیگر پروگارد بهینه کردن کدهای جاوا است. به عبارت دیگر، کدها بازنویسی می‌شوند تا در حد امکان تعداد کاراکترها و دستورات کاهش یافته و حجم فایل DEX اپ به کمترین میزان ممکن برسد.
به عبارتی کدها Minify می‌شوند. درست مانند آنچه در Minify کردن کدهای CSS صفحات وب اتفاق می‌افتد. هنگام Minify کردن یک کد CSS عملیاتی مانند حذف فاصله‌های اضافی و کامنت‌ها انجام می‌شود. همچنین برای مثال اگر برای یک قسمت از صفحه وب، رنگ #FFFFFF تعریف شده باشد، آنرا به #FFF تغییر می‌دهد. همین تغییرات کوچک و به ظاهر غیر ضرور، در نهایت حجم قابل توجهی از فایل نهایی CSS را کاهش می‌دهد.
برای مثال اگر در بخشی از پروژه اندرویدی یک if/else تعریف کرده باشیم و پروگارد تشخیص دهد بلاک else{} هیچگاه اجرا نخواهد شد، آنرا از خروجی برنامه حذف می‌کند. یا مثلا اگر متدی تعریف کرده باشیم که فقط از یک جا فراخوانی می‌شود، پروگارد آنرا حذف کرده و به صورت inline در محل کد اصلی جایگزین می‌کند.
به این ترتیب درصدی از کدهای اضافی پروژه اندرویدی ما حذف شده که علاوه بر کاهش حجم نهایی فایل APK یا AAB برنامه، با بهینه شدن دستورات و کلاس‌های جاوا، سرعت اجرای برنامه می‌تواند افزایش یابد. پروگارد می‌تواند تا ۵۰% از حجم بایت کدهای اپلیکیشن اندرویدی را کاهش دهد.
خب! تا حد زیادی با ProGuard و کاربردهای آن آشنا شدیم. اما لازم است بگویم که پس از انتشار Android Gradle plugin 3.4.0 و همزمان با انتشار Android studio 3.3 beta (در ماه April سال ۲۰۱۹) جایگزینی جدید با نام R8 توسط توسعه دهندگان اندروید استودیو برای ProGuard معرفی شد. این ابزار جدید نسبت به نسخه قبلی برتریی‌هایی را داشته و خروجی بهینه تری را برای ما رقم می‌زند. البته جای هیچ نگرانی نیست زیرا طریق فعالسازی و استفاده آن تفاوتی با پروگارد ندارد.
در واقع R8 از ترکیب و ادغام ProGuard با ابزار دیگر ساخته شده و از قوانین آن پیروی می‌کند. در ادامه بیشتر با R8 آشنا می‌شویم.

ابزار R8 جایگزینی شایسته برای ProGuard

همانطور که قبلا اشاره شد، با انتشار پلاگین گریدل نسخه ۳٫۴٫۰ برای اندروید استودیو، ابزار معروف ProGuard بازنشسته شد و جای خود را به ابزاری تازه نفس و قدرتمندتر به نام R8 داد. ابزار R8 نسبت به پروگارد، عملیات بهینه سازی را با سرعت بالاتری انجام داده و علاوه بر آن، درصد بهینه سازی و کاهش حجم فایل نصبی برنامه نیز افزایش می‌دهد.
شاید بهتر باشد مزایای R8 را نسبت به ProGuard به صورت دقیق‌تر و با جزئیات بیشتری بررسی کنیم.

مقایسه R8 با ProGuard

لازم می‌دانم از کلی گویی اجتناب کرده و تفاوت‌های این دو را به لحاظ فنی و دقیق برای شما تشریح کنم تا درک بهتری از تصمیم گوگل برای جایگزینی R8 داشته باشید. البته عنوان مقایسه خیلی برای این قسمت کامل نیست. می‌خواهم تاریخچه آنچه در طی این چند سال توسعه دهندگان اندروید را از ProGuard به R8 رسانده بیان کنم.
در گذشته روش کار به اینصورت بود که کامپایلر جاوا، سورس کدهای جاوای پروژه اندرویدی را به بایت کد (Bytecode) های جاوا تبدیل می‌کرد. سپس این بایت کدها توسط ProGuard بهینه شده و بایت کدهای بهینه‌ای ایجاد می‌شد که سریعتر و کم حجم تر بودند. در نهایت، کامپایلر DX این بایت کدها را به بایت کدهای ویژه ماشین مجازی دالویک (Dalvik) تبدیل می‌کرد. قبلا در مبحث آشنایی با سیستم عامل اندروید با کاربرد ماشین‌های مجازی Dalvik و ART آشنا شدیم.

کامپایلر DEX اندروید
کامپایلر DEX اندروید استودیو

این بایت کد که با فرمت .dex در پکیج فایل نصبی APK نگهداری می‌شود بسته به نسخه سیستم عامل اندروید دستگاه کاربر، توسط ماشین مجازی Dalvik یا ART و یا ترکیبی از هردو (مانند Android P) به زبان قابل فهم برای ماشین ترجمه می‌شد.
این فرآیند قدری زمان بر بود و در سال ۲۰۱۵ تیم توسعه اندروید تصمیم گرفت کامپایلر جدیدی را جایگزین کند تا مراحل کامپایل کاهش یابد. بنابراین کامپایلری با نام Jack & Jill معرفی شد.

کامپایلر Jack & Jill
کامپایلر Jack & Jill اندروید استودیو

در واقع این کامپایلر ترکیبی از کامپایلر جاوا، پروگارد و کامپایلر دالویک بود و دستورات و توابع هرسه در یک مرحله انجام می‌شد که نتیجه آن افزایش سرعت کامپایل پروژه اندرویدی بود. اما Jack & Jill هم خالی از ایراد نبود و در تعامل با برخی بایت کدهای Java مشکلاتی داشت. به همین دلیل تیم توسعه اندروید این کامپایلر را در سال ۲۰۱۷ کنار گذاشت.
سپس کامپایلر دیگری با نام D8 جایگزین شد که از حیث تعداد مراحل تفاوتی با حالت اول نداشت و صرفا در مرحله آخر، D8 جایگزین DX شده بود.

کامپایلر D8
کامپایلر D8 اندروید استودیو

کامپایلر D8 سازگاری بیشتر بخصوص با کاتلین داشت. علاوه بر آن بایت کدهای کمتر و بهینه تری را تولید می‌کرد.
در نهایت R8 معرفی شد که از ادغام ProGuard و D8 بوجود آمده است.

ابزار R8 اندروید استودیو برای مبهم سازی، بهینه سازی و کوچک کردن کدهای برنامه اندرویدی
کامپایلر R8 اندروید استودیو

در حال حاضر، بایت کدهای جاوا مستقیما توسط کامپایلر R8 به بایت کدهای بهینه دالویک تبدیل می‌شوند. مراحل کمتر، بهینه بودن و سازگاری بیشتر با نیازهای جدید.

مزایای کامپایلر R8

از مهمترین دلایلی که گوگل را مجاب کرد تا ابزار کامپایلر R8 را جایگزین ProGuard در اندروید استودیو کند می‌توان به موارد زیر اشاره کرد:
بهینه سازی بیشتر: R8 در مقایسه با ProGuard بهینه سازی و Minification عمیق‌تری انجام می‌دهد که نتیجه آن فشرده شدنِ بیشترِ نسخه خروجی یعنی فایل APK است. به عبارتی می‌توانیم چند درصد دیگر هم کاهش حجم را هنگام کامپایل کردن پروژه اندرویدی خود شاهد باشیم.
بر اساس تست‌هایی که انجام شده R8 حتی می‌تواند تا ۷۰ درصد از حجم برنامه را نسبت به حالت عادی فشرده‌تر کند که یک خروجی فوق العاده به حساب می‌آید. حتی ۱ درصد کاهش حجم بخصوص در برنامه‌های با حجم بالا می‌تواند در تسریع دسترسی کاربران به اپلیکیشن هنگام دریافت آن از مارکت‌ها و همچنین اجرای برنامه روی دستگاه نقش موثری داشته باشد.
سازگاری بیشتر با Kotlin: با پشتیبانی رسمی محیط توسعه اندروید استودیو از زبان محبوب کاتلین در نسخه‌های جدید، سازگاری هرچه بیشتر کامپایلر با کدهای Kotlin ضروری بود. R8 این ویژگی را دارد و در بهینه کردن کدهایی که به زبان کاتلین نوشته شده‌اند بازدهی بالاتری نسبت به پروگارد دارد.
خروجی بهتر: R8 خروجی بهتری نسبت به پروگارد به ما تحویل می‌دهد. علاوه بر آن، سرعت بیلد شدن پروژه نیز به نسبت قبل مقداری کاهش می‌یابد.

فعال کردن ProGuard / R8 در اندروید استودیو

به اندازه کافی در خصوص پروگارد و جایگزین جدید آن یعنی R8 به تئوریات پرداختیم. بهتر است روش فعالسازی و همچنین جزئیات آن را بررسی کنیم. البته در اینجا می‌خواهیم خروجی پروژه را قبل و بعد از فعال کردن R8 بررسی کنیم تا نتیجه کار را در عمل ببینیم.
مطابق مبحث آموزش ساخت پروژه در اندروید استودیو یک پروژه اندرویدی با نام My Application می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.
یک متغیر از نوع String درون اکتیویتی پیش فرض پروژه تعریف می‌کنم:

MainActivity.java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    String myString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myString = "android-studio.ir";

    }
}

از مسیر Build > Generate Signed Bundle/APK یک خروجی APK از پروژه می‌گیرم.
بعد از ساخته شدن فایل، آنرا تغییر نام می‌دهم تا از خروجی دوم قابل تشخیص باشد. حالا فایل build.gradle (app) پروژه را باز کرده و در بلاک release برای minifiEnabled مقدار true را جایگزین false می‌کنم:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
نکته: در ابتدای مبحث هم اشاره شد که R8 از قوانین ProGuard استفاده می‌کند بنابراین آیتم proguardFiles نباید ابهامی در خصوص اینکه آیا R8 فعال است یا ProGuard برایتان ایجاد کند. البته به شرط آنکه از اندروید استودیو ۳٫۴ به بالا استفاده می‌کنید.
نکته: با انتشار نسخه ۳٫۴ اندروید استودیو و معرفی R8 بجای پروگارد، ابتدا برای فعالسازی آن لازم بود کد زیر در gradle.properties تعریف شود:

android.enableR8=true

اما در نسخه‌های جدید اندروید استودیو این کد کارایی نداشته و صرفا باید مقدار minifyEnabled به true تغییر داده شود.

مجدد یک خروجی APK از پروژه می‌سازم:

مقایسه حجم اپلیکیشن اندرویدی قبل و بعد از فعالسازی ProGuard یا R8
حجم فایل APK قبل و بعد از فعالسازی ProGuard/R8

در تصویر بالا مشاهده می‌کنید حجم فایل APK که در مرحله اول ساخته شده ۲۲۲۱ کیلوبایت است در صورتی که فایل دوم ۱۴۷۰ کیلوبایت حجم دارد. یعنی با فعال شدن R8 در حین کامپایل برنامه چیزی حدود ۳۰ درصد از حجم فایل نصبی برنامه کاهش یافت.
اما همانطور که مفصل بحث کردیم، در کنار بهینه سازی کدها و کاهش حجم نهایی، یک وظیفه دیگر هم بر عهده R8 هست؛ یعنی همان مبهم سازی کدهای جاوا به جهت محافظت از سورس برنامه اندرویدی که در نهایت باعث جلوگیری از دیکد شدن کلاس‌ها، متدها و فیلدها می‌شود.
البته لازم به تکرار است وقتی صحبت از جلوگیری از دیکامپایل شدن اپ می‌کنیم منظور سخت کردن فرایند است وگرنه بطور کامل نمی‌توان از دیکد کردن جاوا و دیکامپایل شدن اپلیکیشن اندرویدی جلوگیری کرد.
برای تست و بررسی فعال شدن R8 در پروژه، هردو فایل APK را توسط APK Decompiler بصورت آنلاین دیکامپایل می‌کنم. این سرویس رایگان و آنلاین از JADX برای دیکامپایل کردن فایل dex موجود در پکیج برنامه اندرویدی و تبدیل آن به کلاس‌های Java استفاده می‌کند.

جلوگیری از دیکامپایل اپ اندرویدی توسط ابزاری مانند JADX یا ApkTool

کافیست فایل apk را انتخاب کرده و روی گزینه Upload and Decompile کلیک کنم تا در ظرف مدت چند ثانیه فایل zip سورس برنامه را تحویل دهد!

دیکامپایل فایل APK توسط ابزار آنلاین
دیکامپایل فایل APK توسط ابزار آنلاین

این کار را برای هردو فایل انجام می‌دهم. فایل‌های zip را باز کرده و بررسی می‌کنم. برای مثال تفاوت کلاس MainActivity به ترتیب، قبل و بعد از فعال شدن R8 به اینصورت است:

package com.example.myapplication;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    String myString;

    /* access modifiers changed from: protected */
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) C0400R.layout.activity_main);
        this.myString = "android-studio.ir";
    }
}
package com.example.myapplication;

import android.os.Bundle;
import p002b.p004b.p005c.C0149h;

public class MainActivity extends C0149h {
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
    }
}

ملاحظه می‌کنید علاوه بر حذف خط کامنت از کلاس، نام کلاس کتابخانه

androidx.appcompat.app.AppCompatActivity

به

p002b.p004b.p005c.C0149h

تغییر یافته. همچنین String ای که داخل اکتیویتی تعریف شده بود نیز حذف شده زیرا R8 تشخیص داده که این متغیر بلا استفاده بوده و در هیچ قسمتی از برنامه فراخوانی نشده است.
نتیجه‌ی این تغییر، کاهش حجم کد از ۴۲۵ کاراکتر به ۲۷۵ کاراکتر و همچنین سخت شدن درک کدها شده است.

فشرده سازی بیشتر با فعال کردن fullMode در R8

اما R8 هنوز هم می‌تواند اپلیکیشن ما را فشرده تر کند. کافیست در فایل gradle.properties خط زیر را اضافه کنیم:

android.enableR8.fullMode=true

توجه داشته باشید این فایل مختص پروژه فعلی نیست و تنظیمات آن در همه‌ی پروژه‌های شما اعمال می‌شود. بنابراین اگر در پروژه دیگری نیاز به فعالسازی fullMode نباشد لازم است این خط را حذف یا کامنت کنیم.

فعال کردن قابلیت fullMode در R8 برای بهینه سازی بیشتر در اندروید استودیو
فعال کردن قابلیت fullMode در R8

مجددا یک خروجی APK از پروژه می‌گیرم تا تفاوت حجم برنامه قبل و بعد از فعال کردن حالت fullMode را ارزیابی کنیم:

کاهش حجم بیشتر در R8 توسط قابلیت fullMode
کاهش بیشتر حجم پس از فعالسازی fullMode

مشاهده می‌کنید بازهم حدود ۱۰۰ کیلوبایت از حجم نهایی اپلیکیشن کاسته شد. به عبارت دیگر با فعال کردن R8 و همچنین حالت fullMode حدود ۴۰ درصد کاهش حجم را در خروجی پروژه شاهد بودیم. البته این اعداد ثابت نیست و بسته به مقدار کدها، تعداد کتابخانه‌های بکار رفته در پروژه، منابع موجود در پروژه مانند تصاویر، فایل‌های صوتی و… متغیر خواهد بود.

تعریف قوانین سفارشی در R8

با فعال کردن ProGuard/R8 در پروژه اندرویدی، هرسه عملکردی که قبلا به آنها اشاره کردیم روی سورس پروژه شامل کلاس‌ها، متدها و فیلدها اعمال می‌شوند. اما گاهی اوقات لازم است یک آیتم را به عنوان استثناء در R8 تعریف کنیم تا هیچگونه تغییری روی آن انجام نگردد.
برای مثال در آموزش کار با کتابخانه Retrofit که شامل یک فرم عضویت بود و اطلاعات شخص شامل نام، نام کاربری و رمز عبور به سرور ارسال می‌شد، نام متغیرهای ارسالی نباید دچار هیچگونه تغییراتی شود در غیر اینصورت اطلاعات عضویت شخص در دیتابیس ثبت نخواهد شد.
اما راهکار چیست؟ آیا به دلیل جلوگیری از بروز اشکال در یک کتابخانه یا کلاس یا تابع باید از بهینه کردن و محافظت از سورس برنامه اندرویدی و جلوگیری از دیکامپایل شدن اپ چشم پوشی کنیم؟ خیـــــر!
در ProGuard و جایگزین آن یعنی R8 قابلیتی در اختیار توسعه دهنده و برنامه نویس اندرویدی قرار گرفته که می‌تواند برای آیتم‌های خاص استثناء قائل شد تا بدون هیچگونه مبهم سازی و یا بهینه سازی در نسخه کامپایل شده برنامه قرار گیرند.
دوباره به بلاک release دقت کنید:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

قوانین و عملکردهای پایه و پیش فرض پروگارد (R8) توسط getDefaultProguardFile از فایلی با نام proguard-android-optimize.txt خوانده می‌شود. اما چنانچه بخواهیم استثنائی در قوانین تعریف کنیم نیازی به ویرایش فایل پیش فرض نیست و لازم است قوانین اختصاصی پروژه را در فایل proguard-rules.pro اضافه کنیم.
این فایل در کنار سایر فایل‌های زیر مجموعه Gradle قرار دارد:

تعریف قوانین سفارشی برای ProGuard/R8 در فایل proguard-rules.pro
محل تعریف قوانین سفارشی برای ProGuard/R8

این فایل به صورت پیش فرض دارای هیچ قانون فعالی نیست و صرفا چند خط توضیحات به صورت کامنت قید شده است. البته در بین توضیحات، چند مورد از قوانین پر کاربرد هم ذکر شده که در صورت نیاز کافیست از حالت کامنت خارج شود:

proguard-rules.pro

# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

برای مثال چنانچه بخواهیم از WebView در برنامه خود استفاده کنیم و صفحه وب موردنظر حاوی کدهای JS (جاوا اسکریپت) باشد، قانونی که بعد از توضیحات مربوط به وب ویو قرار گرفته را از حالت کامنت خارج می‌کنیم:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}

معمولا کتابخانه‌هایی که نیاز به تعریف قوانین اختصاصی دارند توضیحات لازم را در صفحه معرفی خود قید می‌کنند. از جمله Retrofit:

قوانین سفارشی کتابخانه Retrofit برای ProGuard/R8
قوانین سفارشی کتابخانه Retrofit برای ProGuard/R8

ملاحظه می‌کنید در قسمت توضیحات مربوط به قوانین R8 / ProGuard سه لینک ذکر شده که مربوط به قوانین Retrofit و OkHttp و Okio هستند. از آنجایی که کتابخانه‌های OkHttp و Okio در داخل کتابخانه Retrofit قرار دارند، لازم است قوانین هرسه مورد به پروژه اضافه شود.
من برای بررسی بیشتر قوانین، موارد مربوط به خود رتروفیت را اضافه می‌کنم:

proguard-rules.pro

# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

اگر این قوانین برایتان مبهم بنظر می‌رسند جای نگرانی نیست؛ در اینجا به موارد پر کاربرد اشاره می‌کنیم.

قوانین R8 / ProGuard

مهمترین قوانین R8/ProGuard را به طور مختصر توضیح می‌دهم:
-keepattributes: ویژگی‌هایی که توسط این قانون تعریف شوند بدون تغییر باقی می‌مانند.
-keep: توسط این دستور می‌توان یک کلاس و متدها و فیلدهای داخل آن را به عنوان استثناء تعریف کرد تا عملکردهای R8 روی آن پیاده نشود.
-keepclassmembers: متدها و فیلدهای موردنظر داخل یک کلاس را می‌توان به عنوان استثناء تعریف نمود.
-keepnames: از تغییر نام کلاس یا متدها و فیلدهای مدنظر جلوگیری می‌کند.
-dontwarn: تعیین می‌کند که هیچگونه هشداری در مورد ارجاعاتی (References) که در کلاس مدنظر ذکر شده و ProGuard/R8 آن را پیدا نمی‌کند و یا سایر خطاها، داده نشده و از آنها عبور کند.
-dontshrink: این قانون، قابلیت Shrink یعنی حذف آیتم‌های بلا استفاده را غیر فعال می‌کند و صرفا دو عمل دیگر یعنی بهینه سازی (Optimization) و مبهم سازی (Obfuscation) برای آن انجام می‌شود.
-dontoptimize: مانند مورد قبل با این تفاوت که فقط عمل بهینه سازی انجام نمی‌شود.

قوانین پروگارد گسترده تر از چند موردی است که در اینجا اشاره شد. برای مطالعه لیست کامل قوانین و همچنین توضیحات بیشتر به وب سایت آن در صفحه ProGuard Reference card مراجعه کنید.

نکته: با توجه به تغییرات زیادی که توسط R8 در سورس پروژه انجام می‌شود لازم است قبل از انتشار نسخه نهایی اپ جهت ارائه به کاربران، تمامی قسمت‌های آن به دقت تست و بررسی شده و در صورت نیاز، قوانین لازم برای کتابخانه‌ها و کلاس‌ها تعریف شود.

ابزار و روش‌های دیگر جهت محافظت از سورس برنامه اندرویدی

تا اینجا توانستیم با استفاده از ابزار داخلی و رایگان اندروید استودیو تا حد زیادی عمل مبهم سازی و بهینه سازی فایل APK را انجام دهیم. اما جلوگیری از دیکد شدن سورس کد برنامه اندرویدی محدود به همین R8 نیست.
R8 یا ProGuard می‌تواند عملیات مبهم سازی را بر روی کلاس‌ها، فیلدها و متدها پیاده سازی کرده تا درک کدها و سوء استفاده از آنها به راحتی امکان پذیر نباشد. اما این ابزار String ها را بدون تغییر باقی می‌گذارد که در مواردی می‌تواند امنیت برنامه ما را بخطر بیندازد.
برای مثال چنانچه اپلیکیشن شما به یک سرور یا API وابسته است و اطلاعاتی بین آنها ردوبدل می‌شود، در صورتی که آدرس (URL) وب سرویس یا API رمزگذاری (Encrypt) نشده باشد هکر می‌تواند با دیکامپایل کردن برنامه به URL یا کلیدهای خصوصی مربوط به API دسترسی پیدا کرده و از آنها برای مقاصد خود استفاده کند.
چنانچه به قابلیت رمزگذاری رشته‌های متنی برای اپ خود نیاز داشته باشیم لازم است از جایگزین‌های R8 استفاده کرده یا ابزار دیگری را در کنار آن اضافه کنیم.
برای مثال شرکت سازنده ProGuard یعنی GuardSquare یک ابزار دیگر با نام DexGuard را معرفی کرده که امکانات بیشتری نسبت به پروگارد را در اختیار برنامه نویسان اندرویدی قرار می‌دهد. یکی از امکانات دکس گارد رمزگذاری String هاست. البته این ابزار بر خلاف پروگارد رایگان نبوده و باید لایسنس آن خریداری شود.
اما بر خلاف DexGuard که باید جایگزین ProGuard/R8 شود و رایگان هم نیست، پلاگین‌هایی هستند که رایگان بوده و در کنار R8 و به عنوان مکمل استفاده می‌شوند. از جمله پلاگین Enigma یا StringCare که هردو رایگان هستند.
البته نوشتن کدهای حساس و مهم برنامه به صورت native به زبان C/C++ هم می‌تواند فرایند مهندسی معکوس را به مراتب دشوار تر از قبل کند. با استفاده از ابزار NDK می‌توانید کدهای نیتیو خود را در قالب فایل‌های .so به پروژه اندرویدی اضافه کنید.
پرداختن به این ابزار از حوصله این مبحث خارج بوده و صرفا به ذکر نام آنها اکتفا می‌کنم. به امید خدا در آموزش‌های آتی به معرفی کامل و نحوه فعالسازی یکی از آنها خواهیم پرداخت.
موفق و پیروز باشید.

مطالعه‌ی بیشتر:

https://www.guardsquare.com/en/blog/proguard-and-r8
https://www.guardsquare.com/en/blog/comparison-proguard-vs-r8-october-2019-edition
https://developer.android.com/studio/build/shrink-code
https://www.guardsquare.com/en/blog/dexguard-vs-proguard
https://blog.mindorks.com/applying-proguard-in-an-android-application
https://android-developers.googleblog.com/2018/11/r8-new-code-shrinker-from-google-is.html

دانلود نسخه PDF این آموزش
تعداد صفحات : ۲۰
حجم : ۲ مگابایت
قیمت : رایگان
دانلود رایگان با حجم ۲ مگابایت لینک کمکی
این مطلب چقدر برایتان مفید بود؟ لطفا امتیاز دهید
دوره آموزش برنامه نویسی اندروید
دوره آموزش برنامه نویسی اندروید

آموزش‌ها در دو دسته «پایه» و «تکمیلی» منتشر می‌شوند.
آموزش‌های پایه شامل مباحث اصلی و آموزش‌های تکمیلی مطالبی است که می‌بایست در کنار مطالب اصلی از آنها استفاده کنیم.
شما با دریافت این دوره به تمامی آموزش‌های غیر رایگان و رایگان موجود در وب سایت دسترسی دارید که تخفیفی برای آموزش‌های غیر رایگان نیز درنظر گرفته شده. این پکیج به دو صورت دانلودی و ارسال پستی ارائه می‌گردد.
با خرید این دوره، به تمامی آموزش‌های غیر رایگانی که در آینده منتشر می‌شود نیز به صورت رایگان دسترسی خواهید داشت!

یک دیدگاه بنویسید

پرسش‌های زیر تایید و پاسخ داده نـــخواهند شد:
۱: جزء موارد مطرح شده در صفحات مشکلات و پرسش‌های رایج و بروزرسانی‌های محتوای آموزشی باشد
۲: سوال قبلا توسط کاربران در دیدگاه‌ها مطرح و پاسخ داده شده باشد
۳: پرسش خارج از مبحث آموزشی موجود در این صفحه باشد

  • علی رضا گفت:

    سلام امیدوارم این دیدگاه رو کرکر ها نخونن جناب مطهری همینطور که می دونید نرم افرار ها و کرکر های زیادی هستن که میان پرداخت درون برنامه ای رو از بین می برند و به اصطلاح (برنامه/بازی) رو مود می کنند یکی از اون نرم افزار ها نرم افزار معروف لاکی پچر هست وهمینطور که می دونید توی مستندات پرداخت درون برنامه ای استور های ایرانی و گوگل پلی یه کتابخانه به اسم labHelper می دن و تا جایی که من فهمیدم نرم افزار لاکی پچر میاد این کلاس رو هدف می گیره و نابود می کنه من از این طرف یه راه حل پیدا کردم جلوشو بگیریم باید کلاس labHelper رو باز کنیم و متد سازنده(ترجیا سازنده اگه از سایر متد ها هم استفاده کنیم میشه) رو پیدا کنیم و داخلش بگیم که به یک activate دیگه یک پیام بفرسته که بگه این متد اجرا شده وقتی دیدم متد اجرا نشده فورز کلوز میسازیم کرکر فکر کنه خراب شده و نمیشه چون کرکر کلاس LabHelper رو پاک می کنه و اگه نابود شده باشه تشخیص می دیم

  • ramin گفت:

    سلام اقای مطهری ممنون بابت اموزشتون
    لطفا یک اموزش هم برای بحث امنیت در اندروید اپ هایی که با نت و api سروکار دارند و نحوه تبادل اطلاعات با سرور بصورت امن هم قرار بدید.
    تشکر

  • نیما گفت:

    عالی بود
    منتظر همچین اموزشی ازتون بودیم

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *