امنیت اپلیکیشن‌های اندرویدی را با این روش‌ها افزایش دهید

نویسنده : سید ایوب کوکبی ۲۲ مرداد ۱۳۹۷

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

شبکه

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

اولین قدم در برقراری امنیت ارتباطات اینترنتی استفاده از پروتکل ایمن HTTPS به جای HTTP است. این کار لازم بوده ولی کافی نیست. یکی از مشهورترین حملات شبکه، حمله‌ی مرد میانی (MITM: Man-In-The-Middle) است که می‌تواند به دو صورت فعال (Active) و غیرفعال (Passive) صورت گیرد. برای مقابله با حملات غیرفعال MITM می‌توانید از الگوریتم تبادل کلید دیفی-هلمن استفاده کنید.

حملات فعال کمی قوی‌تر بوده که برای مقابله با آن از SSL Pinning استفاده می‌شود. برخی ابزارها از HTTPS و SSL pinning حمایت می‌کنند که Retrofit و OkHttp دو نمونه از آن‌هاست. کتابخانه‌ی رتروفیت، استفاده‌ی آسانی دارد؛ از RxJava پشتیبانی می‌کند و پیکربندی‌اش وقت زیادی نمی‌برد. به کمک OkHttp می‌توانید گواهینامه‌ی SSL معتبر را خودتان اضافه کنید.

به صورت پیش‌فرض، OkHttp به اعتبار گواهینامه‌ی هاست اعتماد می‌کند. SSL pinning امنیت را افزایش می‌دهد ولی قابلیت‌های تیمی سرور به منظور آپدیت گواهینامه‌های TLS را کاهش می‌دهد. از SSL Pinning بدون برکت گرفتن از مدیرِ TLS استفاده نکنید! — جس ویلسون، شرکت Square

Intents

احتمالاً می‌دانید که راه ارتباطی برنامه‌های اندرویدی Intentها هستند. دو نوع اینتنت وجود دارد: ضمنی (Implicit) و صریح (Explicit).

اینتنت‌های صریح (Explicit Intent)

مزایا: شنودشان امکان‌پذیر نیست.

معایب: تنها داخل خود برنامه قابل استفاده است.

اینتنت‌های ضمنی (Implicit Intent)

مزایا: هر جایی داخل سیستم‌عامل اندروید قابل استفاده است.

معایب: به سادگی شنود می‌شوند.

هکر به راحتی می‌تواند با تعریف یک intent-filter مشابه، اینتنت شما را شنود کرده و همه‌ی داده‌هایش را سرقت کند. برای جلوگیری از این کار تا می‌توانید اینتنت‌ها را Explicit یا صریح تعریف کنید. مثلاً می‌توانید یک package مشخص را تعیین کنید.

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setPackage("com.test.package");
sendBroadcast(intent);

سرویس‌ها یا Broadcast Receiverهایی که با کامپوننت‌های خارجی ارتباطی برقرار نمی‌کنند نباید export شوند.

<service android:name=”.service.SomeService” android:enabled=”true” android:exported=”false”>

<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</service>

خلاصه

سعی کنید در برنامه‌ی خود تا آنجا که می‌توانید از Explicit Intentها استفاده کنید. برای حفظ امنیت بیشتر، Receiverای که می‌خواهید اینتنت شما را دریافت کند به صورت صریح مشخص نمایید.

بخوانید  آموزش زبان کاتلین – درس 8 (شرط IF)

ذخیره‌سازی داده

حافظه‌ی پنهان (Memory Cache)

کاربرد حافظه‌ی کش زمانی است که داده‌هایی از سرور دریافت کرده‌اید و می‌خواهید به صورت موقت و دم‌دستی از آن استفاده نمایید. این داده‌ها را می‌توان در کش ذخیره کرد. گاهی اوقات تصویری را پردازش می‌کنید و دوست دارید قسمت‌هایی که به صورت کامل پردازش شده را جای دیگری استفاده کنید. اینجا نیز کش به درد می‌خورد. کش کاربردهای دیگری هم دارد از جمله: ذخیره‌سازی داده‌های کاربر، ID و …

کد پایین مثالی ساده از ساخت یک فایل موقت داخل حافظه‌ی کش را نشان می‌دهد:

File outputDir = this.getCacheDir();
File outputFile = File.createTempFile(“prefix”, “extension”, outputDir);

قطعاً استخراج این فایل‌ها از حافظه‌ی کش و پیدا کردن رمز عبور سخت‌تر از زمانی است که فایل‌ها را به صورت فیزیکی از مموری استخراج کنیم. ضمناً Dump کردن مموری، دسترسی روت می‌خواهد. توصیه می‌کنم مستندات رسمی گوگل درباره‌ی حافظه‌ی کش را حتماً بخوانید.

Chiper

معمولاً قبل از ذخیره کردن داده‌ها در کش، SharedPreference یا دیتابیس، آن‌ها را اینکد می‌کنند که برای این منظور Chiper را توصیه می‌کنم. Chiper ابزاری مفید جهت encode/decode داده‌هاست (اطلاعات بیشتر).

مثالی از کاربرد Chiper:

private static byte[] encrypt(byte[] key, byte[] input) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(input);
return encrypted;
}

private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}

احتمالاً در جریان هستید که برخی از الگوریتم‌های کریپتوگرافی (نظیر AES) نیازمند کلیدی با طول ثابت (۱۲۸, ۱۹۲, ۲۵۶ و …) هستند. برای کسب اطلاعات بیشتر راجع الگوریتم‌هایی که Chiper حمایت می‌کند اینجا را بخوانید.

Shared Preference

همانند مثال‌های قبلی هیچگاه متن معمولی را داخل Shared Preference قرار ندهید. همه‌ی داده‌ها را رمزنگاری کنید. هر چیزی که می‌خواهید داخلش ثبت کنید در حالت MODE_PRIVATE ذخیره نمایید. این مد اطمینان می‌دهد که تنها برنامه‌ی شما قادر است به داده‌های Shared Preference دسترسی داشته باشد. و البته بدون دسترسی روت هیچ کسی نمی‌تواند به این داده‌ها دستبرد بزند. در همین رابطه ابزار دیگری تحت عنوان SecureSharedPreference وجود دارد که کار را برای شما آسان‌تر می‌کند.

این ابزار بر مبنای همان SharedPreference بنا شده ولی با کلی الگوریتم‌های مفید کریپتوگرافی. نیازی نیست که به استفاده از چیز جدیدی عادت کنید، دقیقاً به همان صورت سابق از Shared Preference  استفاده می‌کنید:

SharedPreferences prefs = new SecurePreferences(context, "userpassword", "my_user_prefs.xml");

اما بعد از اینکریپت شدن، نتیجه چنین چیزی است:


<map>
<string name=”TuwbBU0IrAyL9znGBJ87uEi7pW0FwYwX8SZiiKnD2VZ7″>
pD2UhS2K2MNjWm8KzpFrag==:MWm7NgaEhvaxAvA9wASUl0HUHCVBWkn3c2T1WoSAE/g=rroijgeWEGRDFSS/hg
</string>
<string name=”۸lqCQqn73Uo84Rj”>k73tlfVNYsPshll19ztma7U>
</map>

کلیدها

امنیت کلیدها

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

بخوانید  پیش گفتار:چرا من باید برنامه نویس اندروید بشم؟!

می‌توانید از KeyStore استفاده کنید ولی فقط از Android API 18 به بعد قابل استفاده است. و مسئله‌ی بعدی روت بودن دستگاه است. اگر دستگاه روت باشد، هکر به هر چیزی دسترسی دارد. یا می‌توانید از (JNI: Java Native Interface) استفاده کنید. دیکامپایل کردن کدهای ++C/C دشوارتر است. در این حالت دیکامپایلرهایی مثل JaDx و dex2jar کمکی نمی‌کنند چون مبتنی بر جاوا هستند.

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

خلاصه

الگوریتم‌های مختلفی برای رمزنگاری و پنهان‌نگاری داده‌ها وجود دارد اما روراست باشیم هیچ یک از آن‌ها تضمین‌کننده‌ی امنیت مطلق نیست. اما چه می‌شود کرد. به هر حال از عریان رها کردن برنامه که بهتر است نه؟

چند پیشنهاد دیگر برای امنیت بیشتر

راهکارهای دیگری نیز برای برقراری امنیت اپلیکیشن شما وجود دارد که در ادامه به تعدادی اشاره می‌کنم.

Emulator

چک کنید برنامه روی ایمیولیتور در حال اجراست یا نه. که اگر پاسخ مثبت باشد می‌توانید جلوی استفاده از برنامه را بگیرید. برای این هدف می‌توانید از کلاس Build از پکیج android.os استفاده کنید. این کلاس اطلاعات زیادی درباره‌ی دستگاهی که برنامه روی آن در حال اجراست به شما می‌دهد. اگه علاقه‌مند بودید اینجا را مطالعه کنید.

Debug

بررسی کنید دیباگر متصل است یا نه. راحت‌ترین روش استفاده از این کد است:

Debug.isDebuggerConnected()

Root

چک کنید دستگاه کاربر روت شده است یا نه. می‌توانید در صورت روت بودن دستگاه اجازه‌ی اجرای برنامه را ندهید. برای تشخیص روت بودن گوشی می‌توانید از این کد استفاده کنید:

private static boolean isRooted() {
return findBinary("su");
}

public static boolean findBinary(String binaryName) {
boolean found = false;
if (!found) {
String[] places = {"/sbin/", "/system/bin/",
"/system/xbin/", "/data/local/xbin/",
"/data/local/bin/", "/system/sd/xbin/",
"/system/bin/failsafe/", "/data/local/"};
for (String where : places) {
if (new File(where + binaryName).exists()) {
found = true;
break;
}
}
}
return found;
}

این کد را از کتابخانه‌ی RootTools قرض گرفته‌ام.

دستور su معمولاً برای تغییر مالکیت از کاربر ارجینال به کاربر روت استفاده می‌شود. (اطلاعات بیشتر)

بخوانید  بهترین روش نام‌گذاری ریسورسها در اندروید

در صورت True بودن نتیجه‌ی این متد، می‌توانید سرور را مطلع کرده، وقفه‌ای صادر کنید یا کلاً برنامه را کرش نمایید. اما زیاده‌روی نکنید، سخت‌گیری بیش از حد باعث تجربه‌ی کاربری بد و روبرگردانی کاربران از برنامه‌ی شما شود.

رشته‌ها

به جای لیترال‌های متنی از آرایه‌ی کاراکترها استفاده کنید. یا با عملگر new رشته تعریف کنید. در این حالت رشته به جای ذخیره شدن در string pool داخل heap ذخیره می‌شود.

String a = new String("a");
a = new String("a");

در مثال بالا، دو آبجکت مختلف ایجاد می‌شود. بعد از اجرای سطر دوم کد، GC یا Garbage Collector می‌تواند شی اولی را حذف کند تا حافظه آزاد شود.

String a = "a";
String b = "a";

در این مثال، تنها یک آبجکت ساخته خواهد شد. از آنجایی که رشته به صورت لیترال می‌باشد، آبجکت شما در string pool ذخیره می‌شود و GC فوراً اقدام به از بین بردن آن نمی‌کند.

مبهم‌سازی (Obfuscation)

از آبفسکیتورهای نظیر ProGuard, DexGuard, DexProtector حتماً استفاده کنید. ProGuard آبفسکیتور پیش‌فرض اندروید استودیو است. این ابزار ضمن مبهم کردن کدها، حجم آن را نیز کاهش می‌دهد. در هنگام ساخت پروژه‌های اندروید، فایل کانفیگ پروگارد نیز به صورت خودکار ساخته می‌شود. اسم فایل proguard-rules.pro است. اینجا می‌توانید قواعدی تعریف کنید که در هنگام Build کردن APK اجرا شوند. قواعد تعدادی از این کتابخانه‌ها را می‌توانید اینجا ببینید.

برای فعال کردن پروگارد از این کد استفاده کنید:

android {
buildTypes {
dev {
minifyEnabled true // enables ProGuard
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

کدهای بهم‌ریخته، حسابی وقت هکر را می‌گیرد و در اغلب مواقع به خاطر وقت و زحمت زیادی که رمزگشایی این کدها لازم دارد او را از ادامه‌ی کار منصرف می‌کند مگر اینکه برنامه‌ی شما واقعاً ارزش این همه وقت‌گذاشتن را داشته باشد. البته به خاطر همه‌گیر شدن استفاده از پروگارد سعی کنید از Obfuscator های ناشناخته‌تر استفاده کنید که هنوز Deobfuscatorهایی برایشان عرضه نشده است. لیستی از سایر جایگزین‌های پروگارد را ببینید.

بهترین‌هایشان معمولاً گران هستند ولی می‌توانند کل برنامه‌ی شما را غیرقابل درک کرده و داخل یک فایل ذخیره کنند. ام این پایان راه نیست، چک کردن روت، دیباگ، ایمیولیتور، MITM و اصلاح کدها از دیگر قابلیت‌های این ابزارهاست که قیمتشان را بیشتر می‌کند.

چند لینک مفید

مستندات رسمی گوگل درباره‌ی امنیت برنامه‌های اندروید

۱۰ ریسک موبایل

ابزارهای تست امنیت برنامه

خلاصه

در این مقاله سعی کردم به صورت فشرده سرنخ‌هایی برای مطالعه بیشتر در حوزه امنیت اپلیکیشن‌های اندرویدی ارائه دهم. متأسفانه اکثر تکنیک‌های برقراری امنیت با دسترسی روت به دستگاه توسط هکرها نقشه برآب می‌شود. امیدواریم در نسخه‌های بعدی اندروید، گوگل فکری به حال این معضل بکند.

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

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