چرا امنیت در برنامه نویسی اندروید و حفاظت از داده ها حیاتی است؟
امنیت در برنامه نویسی اندروید مجموعه ای از راهکارهای دفاعی برای محافظت از اطلاعات حساس کاربران، توکن های احراز هویت و جلوگیری از نفوذ به لایه های داده است. با توجه به متن باز بودن پلتفرم اندروید و امکان دسترسی روت، پیاده سازی تدابیر امنیتی مبتنی بر سخت افزار برای حفظ اعتبار اپلیکیشن ضروری است.
بسیاری از توسعه دهندگان تصور می کنند ذخیره سازی توکن ها در SharedPreferences یا ذخیره ساده در دیتابیس لوکال برای پنهان سازی داده ها کافی است. در صورتی که با دی کامپایل فایل APK یا روت شدن دستگاه، تمامی این اطلاعات به راحتی استخراج می شوند.
بر اساس گزارش های مراکز امنیت سایبری، بیش از ۹۴ درصد اپلیکیشن های مالی و تجاری تست شده در سال های اخیر دارای آسیب پذیری های مرتبط با ذخیره سازی ناامن اطلاعات بوده اند. با بهره گیری از کلیدهای رمزنگاری سخت افزاری، ریسک افشای دیتای محلی تا ۹۹.۴ درصد مهار می شود.
حملات سایبری روی کلاینت های موبایل معمولا در سه جبهه اصلی رخ می دهند: استخراج داده های ساکن روی حافظه، شنود اطلاعات در حال انتقال روی بستر شبکه، و دستکاری پویای منطق برنامه در زمان اجرا توسط ابزار هایی نظیر Frida.
در مهندسی نرم افزار و بر اساس الگو های معماری تمیز، لایه های امنیتی به عنوان سرویس های مستقل در لایه داده پیاده سازی می شوند تا کل پروژه از پایداری یکپارچه بهره مند شود.
در این راهنما، تمامی ارکان امنیت موبایل از سیستم کلیدداری سخت افزاری تا رمزنگاری متقارن مدرن و پینینگ شبکه را با زبان کاتلین بررسی خواهیم کرد.
کالبدشکافی Android KeyStore و ماژول های سخت افزاری TEE
سیستم Android KeyStore یک کانتینر فوق امنیتی در سیستم عامل اندروید است که برای ایجاد و ذخیره سازی کلیدهای رمزنگاری طراحی شده است. بارزترین ویژگی KeyStore این است که کلیدهای خصوصی هرگز وارد فضای حافظه رم اپلیکیشن نمی شوند و استخراج فیزیکی آن ها غیرممکن است.
هنگامی که شما یک کلید در KeyStore ایجاد می کنید، عملیات رمزنگاری و رمزگشایی در یک پردازنده امن به نام محیط اجرای معتبر (Trusted Execution Environment یا TEE) یا ماژول سخت افزاری مستقل StrongBox انجام می گیرد.
اپلیکیشن صرفا درخواست رمزنگاری بایت ها را به ماژول ارسال می کند و نتیجه رمز شده را دریافت می نماید. حتی اگر مهاجم با دسترسی روت کدهای برنامه را در حافظه دستکاری کند، به کلید اصلی ۲۵۶ بیتی دسترسی نخواهد داشت.
تولید کلیدهای رمزنگاری در اندروید با استفاده از کلاس KeyGenParameterSpec مدیریت می شود که در آن الگوریتم های مجاز، طول کلید و شرایط احراز هویت بیومتریک تعریف می گردند.
همچنین در دستگاه های پرچمدار مدرن، چیپ های اختصاصی StrongBox با کریپتوپردازنده های ایزوله و حافظه رم مقاوم در برابر دستکاری های فیزیکی تعبیه شده اند که بالاترین استاندارد امنیتی سخت افزار موبایل را محقق می سازند.
پیاده سازی رمزنگاری متقارن با الگوریتم AES-۲۵۶ GCM در کاتلین
الگوریتم AES در حالت GCM (Galois/Counter Mode) استاندارد طلایی رمزنگاری متقارن در برنامه نویسی اندروید محسوب می شود. این الگوریتم علاوه بر محرمانگی بالا، اصالت داده ها (Authenticity) را با یک تگ تاییدیه ۱۲۸ بیتی تضمین کرده و مانع از دستکاری سایفرتکست می شود.
در حالت های قدیمی تر نظیر CBC، امکان دستکاری داده های رمز شده بدون متوجه شدن برنامه وجود داشت، اما در AES-GCM کوچک ترین تغییر در بایت ها به رد قطعی عملیات رمزگشایی منجر می شود.
یکی از مهم ترین الزامات امنیتی در AES-GCM، استفاده از یک وکتور اولیه تصادفی (Initialization Vector یا IV) با طول ۱۲ بایت برای هر بار عملیات رمزنگاری است. استفاده مجدد از یک IV با کلید یکسان، امنیت سیستم را به شدت تضعیف می کند.
AES/ECB/PKCS5Padding به هیچ وجه داده ها را پنهان نمی کند و الگو های بصری اطلاعات در سایفرتکست قابل شناسایی است. در تمام پروژه ها الزاما از AES/GCM/NoPadding استفاده کنید.
کد زیر کلاس مدیریت رمزنگاری سخت افزاری با نام CryptoManager را در زبان کاتلین نمایش می دهد:
class CryptoManager {
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
private fun getOrCreateSecretKey(): SecretKey {
val existingKey = keyStore.getEntry("secret_app_key", null) as? KeyStore.SecretKeyEntry
return existingKey?.secretKey ?: generateKey()
}
private fun generateKey(): SecretKey {
return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore").apply {
init(
KeyGenParameterSpec.Builder("secret_app_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
)
}.generateKey()
}
}
پس از ایجاد کلید اختصاصی در سخت افزار امنیتی، متدهای رمزنگاری و رمزگشایی جریان بایت ها را با وکتورهای تصادفی مدیریت می کنند.
fun encryptData(bytes: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.ENCRYPT_MODE, getOrCreateSecretKey())
}
val iv = cipher.iv // وکتور ۱۲ بایتی تولید شده
val encrypted = cipher.doFinal(bytes)
return iv + encrypted // اتصال IV به ابتدای داده رمز شده
}
fun decryptData(encryptedBytes: ByteArray): ByteArray {
val iv = encryptedBytes.copyOfRange(0, 12)
val cipherText = encryptedBytes.copyOfRange(12, encryptedBytes.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.DECRYPT_MODE, getOrCreateSecretKey(), GCMParameterSpec(128, iv))
}
return cipher.doFinal(cipherText)
}
این پیاده سازی تضمین می کند که حتی در صورت ذخیره سازی طولانی مدت داده ها روی دیسک، هیچ مهاجمی توانایی دستکاری یا بازگشایی متن اصلی را بدون کلید مستقر در سخت افزار نخواهد داشت.
احراز هویت بیومتریک و اتصال سنسور اثر انگشت به CryptoObject
احراز هویت بیومتریک در اپلیکیشن های مدرن اندروید از طریق کتابخانه استاندارد androidx.biometric.BiometricPrompt پیاده سازی می شود. برای دستیابی به حداکثر امنیت، تایید اثر انگشت نباید صرفا یک شرط نمایشی (Boolean) در کدهای برنامه باشد.
در شیوه مهندسی پیشرفته، کلید رمزنگاری در KeyStore به گونه ای پیکربندی می شود که استفاده از آن مشروط به تایید موفقیت آمیز بیومتریک باشد (setUserAuthenticationRequired(true)).
در این حالت، یک نمونه از شی Cipher داخل کلاس BiometricPrompt.CryptoObject قرار داده شده و به دیالوگ سیستم عامل تحویل داده می شود. کلید فقط زمانی توسط TEE فعال می گردد که اسکنر انگشت هویت معتبر را تایید نماید.
این ساختار مانع از نفوذ ابزار هایی نظیر Frida و Xposed می شود، چرا که حتی با بای پس کردن کدهای کاتلین، چیپست سخت افزاری تا زمان لمس واقعی سنسور اجازه رمزگشایی به کلید نمی دهد.
پیاده سازی دیالوگ بیومتریک در لایه نمایش با کاتلین به شکل زیر انجام می پذیرد:
fun showBiometricAuth(
activity: FragmentActivity,
cipher: Cipher,
onSuccess: (Cipher) -> Unit
) {
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("احراز هویت بیومتریک")
.setSubtitle("برای مشاهده داده های حساس انگشت خود را اسکن کنید")
.setNegativeButtonText("انصراف")
.build()
val prompt = BiometricPrompt(activity, ContextCompat.getMainExecutor(activity),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
result.cryptoObject?.cipher?.let { onSuccess(it) }
}
})
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
این فرآیند هماهنگی کامل با مدیریت کارهای ناهمگام و کورتین ها و Flow دارد تا پاسخ احراز هویت بدون مسدودسازی رابط کاربری به وضعیت برنامه منتقل شود.
امنیت ارتباطات شبکه و پیاده سازی Certificate Pinning در OkHttp
حملات مرد میانی (Man-in-the-Middle یا MitM) یکی از متداول ترین روش ها برای سرقت اطلاعات در حال تبادل بین کلاینت اندروید و سرور است. مهاجمان با نصب گواهی های ساختگی CA در سیستم عامل یا استفاده از نرم افزارهای پروکسی، پکت های رمزنگاری شده SSL/TLS را شنود می کنند.
برای خنثی سازی این تهدید، تکنیک سنجش گواهی (Certificate Pinning) به کار گرفته می شود. در این روش، اپلیکیشن هش عمومی (SHA-۲۵۶) گواهینامه سرور را در کدهای خود ثبت می کند و در زمان دست تکانی TLS، اصالت گواهی را می سنجد.
هنگام استفاده از کتابخانه رتروفیت Retrofit، کلاس CertificatePinner در کلاینت زیرین OkHttp پیکربندی می شود. در صورتی که هش ارسالی سرور با پین مطابقت نداشته باشد، ارتباط شبکه فورا در عرض چند میلی ثانیه قطع می گردد.
val certificatePinner = CertificatePinner.Builder()
.add("api.vahidhoseini.ir", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.vahidhoseini.ir", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // Backup pin
.build()
val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
همواره یک پین رزرو (Backup Pin) از کلیدهای آتی سرور را در کد قرار دهید تا در صورت تمدید یا تعویض سالانه گواهینامه SSL، دسترسی کاربران فعلی به سرور مسدود نشود.
علاوه بر این، در فایل network_security_config.xml اندروید باید قابلیت اعتماد به گواهینامه های کاربری (User Certificates) مسدود شود تا حتی با نصب گواهی دست ساز روی گوشی، امنیت برنامه حفظ گردد.
ذخیره سازی امن داده ها با EncryptedSharedPreferences و SQLCipher
ذخیره سازی فایل های متنی و رکوردهای پایگاه داده در حافظه داخلی گوشی باید با استفاده از کلیدهای رمزنگاری محافظت شود. گوگل برای ذخیره جفت های کلید-مقدار، کتابخانه EncryptedSharedPreferences را در پکیج Androidx Security ارائه داده است.
این کتابخانه با استفاده از الگوریتم MasterKey متصل به KeyStore، نام کلیدها و مقادیر را پیش از نوشتن روی حافظه فلش با الگوریتم های پیشرفته AES-SIV و AES-GCM رمزنگاری می کند.
همچنین برای حفاظت از داده های جدولی با حجم بیش از چند مگابایت در دیتابیس Room، از افزونه کتابخانه ای SQLCipher بهره گرفته می شود.
// ساخت کلید ارشد در سخت افزار
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
// نمونه سازی SharedPreferences رمزنگاری شده
val securePrefs = EncryptedSharedPreferences.create(
context,
"secure_user_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// ذخیره ایمن توکن کاربر
securePrefs.edit().putString("auth_token", "jwt_secret_token_123").apply()
با این ساختار، حتی در صورت پشتیبان گیری از فایل های برنامه با دستورات ADB، دیتای ذخیره شده به صورت نامفهوم باقی خواهد ماند.
رمزنگاری پایگاه داده محلی Room با موتور SQLCipher
پایگاه داده روم به صورت پیش فرض داده ها را در قالب یک فایل خام SQLite روی حافظه دستگاه ذخیره می کند. اگر گوشی روت شود، مهاجم می تواند این فایل را با دستورات خط فرمان کپی کرده و به تمامی جداول، تراکنش ها و تاریخچه ها دسترسی پیدا کند.
موتور امنیتی SQLCipher یک الحاقیه قدرتمند است که تک تک صفحات فایل دیتابیس (Page-level Encryption) را با الگوریتم ۲۵۶ بیتی AES رمزنگاری می کند. این کار بدون ایجاد تغییر در کدهای DAO روم انجام می پذیرد.
رمز عبور یا کلید فعال سازی دیتابیس نباید در کدهای جاوا یا کاتلین هاردکد شود؛ بلکه این عبارت عبور خود باید در زمان اجرای اولیه توسط KeyStore تولید شده و به کارخانه ساخت روم تزریق گردد.
// تنظیم کارخانه رمزنگاری SQLCipher در دیتابیس Room
val passphrase = cryptoManager.getDatabasePassphrase()
val factory = SupportFactory(passphrase)
val db = Room.databaseBuilder(context, AppDatabase::class.java, "encrypted_app.db")
.openHelperFactory(factory)
.build()
این رویکرد ترکیبی، سرعت فوق العاده کوئری های روم را با امنیت پردازنده های سخت افزاری پیوند می زند و خیال توسعه دهنده را از حفاظت داده های حجیم آسوده می سازد.
در صورتی که اپلیکیشن شما دارای نسخه ای در حال اجرا در مارکت هاست که قبلا دیتای بدون رمز داشته است، فرآیند انتقال به SQLCipher نیازمند یک گام صادراتی (Export) است.
با فراخوانی متد sqlcipher_export در روم، رکوردهای قبلی به فایل رمزشده جدید منتقل می شوند و فایل خام دیتابیس قدیمی برای همیشه از حافظه دستگاه پاکسازی می گردد.
تکنیک های ضد مهندسی معکوس و شناسایی دستگاه های روت شده
اپلیکیشن های حساس نظیر سامانه های پرداختی و کیف پول های دیجیتال باید وضعیت سلامت محیط عامل خود را ارزیابی کنند. در دستگاه های روت شده، دسترسی های سیستمی سوپریوزر (SU) امکان پایش حافظه رم و دور زدن لایه های منطقی برنامه را فراهم می سازد.
بررسی وجود باینری های روت نظیر /system/bin/su یا پوشه های خطرناک Magisk و KernelSU از ساده ترین روش های تشخیص دستکاری است. همچنین با بررسی تگ های بیلد سیستم عامل (Test-Keys) می توان رام های کاستوم دستکاری شده را شناسایی کرد.
object RootDetector {
fun isDeviceRooted(): Boolean {
val paths = arrayOf(
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su"
)
return paths.any { File(it).exists() } || checkBuildTags()
}
private fun checkBuildTags(): Boolean {
val buildTags = android.os.Build.TAGS
return buildTags != null && buildTags.contains("test-keys")
}
}
علاوه بر این، در زمان بیلد خروجی با ابزار R۸ و ProGuard، کدهای برنامه فشرده و مبهم سازی (Obfuscate) می شوند تا نام کلاس ها و توابع به حروف تک حرفی بی معنا تبدیل شده و تحلیل آن در دیکامپایلرها دشوار گردد.
مقایسه جامع راهکارهای ذخیره سازی و رمزنگاری در اندروید
انتخاب تکنولوژی ذخیره سازی مناسب بستگی به حجم داده، حساسیت اطلاعات و پیچیدگی های پیاده سازی دارد. در جدول زیر راهکارهای متداول اندروید مقایسه شده اند:
| روش ذخیره سازی | سطح امنیت | حفاظت در برابر روت | سرعت دسترسی | کاربرد اصلی |
|---|---|---|---|---|
| SharedPreferences ساده | بسیار ضعیف (متن خام) | صفر | بسیار سریع | تنظیمات ظاهری و تم برنامه |
| EncryptedSharedPreferences | عالی (سخت افزاری) | بسیار بالا | سریع با تاخیر کم | توکن احراز هویت و API Key |
| Room بدون رمزنگاری | متوسط (محلی) | ضعیف | فوق العاده بالا | کش داده های عمومی و اخبار |
| Room با افزونه SQLCipher | فوق العاده عالی (AES-۲۵۶) | بسیار بالا | کاهش نامحسوس سرعت | پیام های چت خصوصی و اطلاعات مالی |
| Android KeyStore خالص | بالاترین سطح (TEE/StrongBox) | ایزولاسیون کامل | نیاز به پردازش سخت افزاری | نگه داری کلیدهای اصلی امضا و رمزنگاری |
ترکیب هوشمندانه Android KeyStore برای نگه داری کلید ارشد همراه با EncryptedSharedPreferences، نقطه تعادل بی نقصی بین سرعت اجرا و امنیت سخت افزاری ایجاد می کند.
اشتباهات مرگبار در پیاده سازی امنیت اندروید و شیوه اجتناب از آن ها
بسیاری از باگ های امنیتی در اپلیکیشن های اندروید نه به دلیل ضعف فریم ورک، بلکه ناشی از خطاهای کد نویسی و فرضیات اشتباه توسعه دهندگان است.
نخستین اشتباه مرگبار، هاردکد کردن کلیدهای رمزنگاری یا توکن های API در کدهای کاتلین یا فایل های strings.xml است. هر رشته ای که در کدهای جاوا یا کاتلین نوشته شود، با نرم افزارهای رایگانی نظیر JADX در کمتر از چند ثانیه استخراج می گردد.
دومین اشتباه، پذیرش تمام گواهینامه های SSL در زمان تست با کدهایی نظیر TrustAllCerts است که متاسفانه گاهی به نسخه پروداکشن راه پیدا می کند و اپلیکیشن را در برابر حملات MitM کاملا بی دفاع می سازد.
سومین خطا، ذخیره سازی کلیدهای احراز هویت در دیتابیس بدون اعمال وکتور تصادفی (IV) است که امکان حملات تحلیل فرکانسی را برای نفوذگران فراهم می آورد.
امنیت حافظه رم، پاکسازی داده ها و محافظت در برابر اسکرین شات
حفاظت از اطلاعات حساس تنها به ذخیره سازی روی حافظه فلش محدود نمی شود؛ بلکه حفاظت از بایت های مقیم در حافظه رم نیز از اولویت های امنیتی است. اشیای از نوع String در استخر حافظه JVM به صورت تغییرناپذیر باقی می مانند و تا زمان اجرای Garbage Collector در دامپ رم قابل رویت هستند.
توسعه دهندگان سیستم های بانکی برای متغیرهای پسورد از آرایه های CharArray یا ByteArray استفاده می کنند. به این ترتیب پس از ارسال درخواست به سرور، بایت های حافظه بلافاصله با مقادیر صفر بازنویسی (Memory Scrubbing) می شوند.
همچنین برای جلوگیری از عکس برداری ناخواسته از صفحات تراکنش و ممانعت از نمایش اطلاعات در لیست برنامه های اخیر سیستم عامل، پرچم امنیتی FLAG_SECURE در اکتیویتی تنظیم می گردد.
// پاکسازی امن آرایه بایت از رم
fun clearSensitiveMemory(bytes: ByteArray) {
java.util.Arrays.fill(bytes, 0.toByte())
}
// فعال سازی محافظت پنجره در برابر اسکرین شات
fun secureWindow(activity: Activity) {
activity.window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
}
این تکنیک ساده تضمین می کند که بدافزارهای جاسوسی مستقر در گوشی و ابزار های مانیتورینگ صفحه نمایش توانایی سرقت اطلاعات بصری را نخواهند داشت.
شناسایی ابزار های دیباگ دینامیک و فریم ورک های هوک نظیر Frida
ابزار های تحلیل پویای کد مانند Frida و Xposed به هکرها امکان می دهند که در زمان اجرای اپلیکیشن، متدهای جاوا و کاتلین را هوک کرده و خروجی های اعتبارسنجی را به دلخواه خود تغییر دهند.
برای مقابله با این روش ها، اپلیکیشن در زمان آغاز به کار پورت های پیش فرض سرور هایی فریدا (نظیر پورت ۲۷۰۴۲) و وجود فایل های مشکوک در مسیر لود ماژول های فرآیند را بررسی می کند.
همچنین با بررسی پرچم های رسمی دیباگینگ سیستم عامل، از متصل نبودن دیباگرهای سیستمی به پروسه اطمینان حاصل می شود و در صورت کشف هرگونه ناهنجاری، فرآیند اجرای برنامه بلافاصله متوقف می گردد.
// ارزیابی اتصال ابزار های تحلیل پویا و پورت های فریدا
object TamperDetector {
fun isFridaServerRunning(): Boolean {
return try {
val socket = java.net.Socket("127.0.0.1", 27042)
socket.close()
true
} catch (e: Exception) {
false
}
}
fun isDebuggerActive(): Boolean {
return android.os.Debug.isDebuggerConnected()
}
}
ترکیب این اعتبارسنجی ها با مکانیزم های لایه سخت افزاری KeyStore، سد دفاعی مستحکمی در برابر انواع حملات فعال ایجاد می نماید.
تزریق وابستگی ماژول های امنیتی با فریم ورک Hilt
برای حفظ اصول طراحی تمیز و پرهیز از نمونه سازی های تکراری، کلاس های امنیتی نظیر CryptoManager و کلاینت امن شبکه باید از طریق فریم ورک های تزریق وابستگی مدیریت شوند.
با بهره گیری از تزریق وابستگی با Hilt، نمونه های مورد نیاز به صورت تکین (Singleton) در دامنه کل اپلیکیشن ارائه شده و در لایه داده و مخازن مصرف می گردند.
@Module
@InstallIn(SingletonComponent::class)
object SecurityModule {
@Provides
@Singleton
fun provideCryptoManager(): CryptoManager = CryptoManager()
@Provides
@Singleton
fun provideSecurePreferences(
@ApplicationContext context: Context,
masterKey: MasterKey
): SharedPreferences {
return EncryptedSharedPreferences.create(
context,
"vault_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
}
این جداسازی تمیز، تست پذیری لایه های داده را ارتقا داده و امکان شبیه سازی ماژول های رمزنگاری در محیط های تستی را فراهم می سازد.
چک لیست ممیزی امنیت اندروید قبل از انتشار در پروداکشن
پیش از آپلود نهایی فایل APK یا AAB در استورهای کافه بازار، مایکت یا گوگل پلی، رعایت معیارهای زیر را ممیزی فرمایید:
- غیرفعال بودن فلش اشکال زدایی (Debuggable = false): ویژگی
android:debuggableدر مانیفست ریلیز خاموش باشد. - مسدود بودن ترافیک متنی خام (Cleartext): اتریبیوت
usesCleartextTrafficبرابر باfalseقرار گیرد تا فقط ارتباطات HTTPS مجاز باشد. - فعال بودن ابزار R۸ و مبهم سازی کد: فایل
proguard-rules.proبا قوانین سخت گیرانه فعال باشد. - استفاده از Android KeyStore برای تمام کلیدها: هیچ کلید متقارنی روی دیسک یا کد ذخیره نشده باشد.
- اعتبارسنجی پین گواهی شبکه: دامنه های اصلی به همراه کلیدهای پشتیبان پین شده باشند.
- عدم ثبت اطلاعات حساس در لاگ کت (Logcat): تمام توکن ها و کلمات عبور از توابع لاگ حذف شوند.
- بررسی مجوزهای مانیفست: مجوزهای خطرناک نظیر دسترسی به لوکیشن یا دوربین فقط در زمان نیاز درخواست شوند.
- تست نفوذ با ابزار های خودکار: پکیج برنامه با ابزار هایی مانند MobSF ممیزی گردد.
استراتژی چرخش کلیدهای رمزنگاری و مهاجرت داده های قدیمی
چرخش کلیدهای رمزنگاری یکی از ضروری ترین اصول امنیتی در چرخه حیات نرم افزارهای مالی و انترپرایز است. با گذشت زمان یا در صورت مشکوک شدن به افشای احتمالی داده ها، باید بتوان بدون ایجاد اختلال در تجربه کاربر، کلیدهای جدیدی تولید و اطلاعات قبلی را باز رمزنگاری کرد.
برای پیاده سازی چرخش کلید در اندروید، ساختار متادیتای هر بسته رمز شده را همراه با شماره نسخه کلید ذخیره می کنیم. به عنوان مثال، اگر داده ای با کلید نسخه ۱ رمز شده باشد، تگ v1 در هدر بایت ها ثبت می گردد.
در زمان خواندن داده، چنانچه نسخه کلید با آخرین نسخه فعال تطابق نداشته باشد، ابتدا بایت ها با استفاده از کلید قدیمی رمزگشایی می شوند و بلافاصله با کلید جدید AES-256 بازتولید شده و روی حافظه دیسک به روزرسانی می گردند.
// پیاده سازی الگوی مهاجرت و چرخش کلیدهای رمزنگاری
class KeyRotationManager(
private val cryptoManager: CryptoManager
) {
fun migrateCiphertext(
oldCiphertext: ByteArray,
fromKeyAlias: String,
toKeyAlias: String
): ByteArray {
val decryptedBytes = cryptoManager.decryptWithAlias(oldCiphertext, fromKeyAlias)
val reEncryptedBytes = cryptoManager.encryptWithAlias(decryptedBytes, toKeyAlias)
decryptedBytes.fill(0) // پاکسازی رم
return reEncryptedBytes
}
}
با این رویکرد، فرآیند چرخش کلیدها به صورت کاملا نامحسوس و در پس زمینه انجام می گیرد بدون آن که نیازی به خروج اجباری یا پاک کردن کش دیتابیس کاربر باشد.
قوانین بهینه سازی و مبهم سازی با R۸ و ProGuard برای لایه های کریپتوگرافی
ابزار کامپایلر R۸ در اندروید وظیفه کوچک سازی، بهینه سازی و مبهم سازی بایت کدهای کاتلین را بر عهده دارد. با این حال در صورت پیکربندی نادرست فایل قوانین، ممکن است کلاس های رمزنگاری یا توابع امنیتی به درستی کار نکنند یا نام الگوریتم ها حذف شوند.
برای محافظت از اینترفیس های امنیتی در برابر مهندسی معکوس و در عین حال حفظ پایداری سیستم کریپتوگرافی، قوانین اختصاصی زیر را در فایل قوانین پروژه تعریف می کنیم:
# حفظ ماژول های امنیتی و الگوریتم های رمزنگاری
-keepattributes *Annotation*,Signature,InnerClasses
-keepclassmembers class * {
@androidx.annotation.Keep <methods>;
@androidx.annotation.Keep <fields>;
}
# جلوگیری از تغییر نام سرویس های کریپتوگرافی
-keep class javax.crypto.** { *; }
-keep class java.security.** { *; }
-keep class org.bouncycastle.** { *; }
# حذف کامل متدهای لاگینگ در بیلد نهایی
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
دستور فوق تضمین می کند که تمامی فراخوانی های لاگ در نسخه نهایی به صورت خودکار حذف شده و هیچ اطلاعات فنی به کنسول های عمومی نشت پیدا نمی کند.
اصالت سنجی سلامت سیستم عامل و سخت افزار با Play Integrity API
حتی اگر داده ها با قوی ترین الگوریتم ها رمزنگاری شوند، اجرای اپلیکیشن در یک محیط دستکاری شده یا ماشین مجازی آلوده می تواند کل امنیت را زیر سوال ببرد. سرویس Google Play Integrity ابزار رسمی گوگل برای ارزیابی اعتبار محیط کلاینت است.
این سرویس جایگزین مدرن ابزار های قدیمی محسوب می شود و سه لایه اعتبارسنجی را برای اپلیکیشن گزارش می دهد: اصالت برنامه، اصالت دستگاه، و اعتبار حساب کاربری گوگل.
دستگاه هایی که گواهی سخت افزاری دریافت می کنند، نشان دهنده وجود بوت لودر قفل شده، کرنل دستکاری نشده و ماژول سخت افزاری TEE فعال هستند که برای صدور کلیدهای حساس بانکی تایید می شوند.
در صورتی که بررسی ها نشان دهد دستگاه روت شده یا اپلیکیشن توسط مهاجم بازپکیج شده است، کلاینت می تواند از ذخیره سازی کلیدهای احراز هویت خودداری کرده و دسترسی به قابلیت های حساس را محدود نماید.
پرسش های متداول مهندسی امنیت و رمزنگاری در اندروید
تفاوت Android KeyStore با SharedPreferences چیست؟
سیستم SharedPreferences داده ها را به صورت فایل های متنی ساده روی دیسک ذخیره می کند، در حالی که Android KeyStore یک کانتینر ایزوله سخت افزاری است که کلیدهای رمزنگاری را در پردازنده های امن TEE نگه داری کرده و اجازه خروج کلید از سخت افزار را نمی دهد.
آیا داده های رمز شده با KeyStore پس از حذف برنامه باقی می مانند؟
خیر، هنگامی که کاربر اپلیکیشن را از روی دستگاه حذف می کند، سیستم عامل اندروید تمامی کلیدهای تولید شده اختصاصی آن برنامه را از محیط سخت افزاری KeyStore به صورت غیرقابل بازگشت پاکسازی می کند.
چرا نباید از الگوریتم MD۵ یا SHA-۱ برای پسوردها استفاده کرد؟
الگوریتم های MD۵ و SHA-۱ دچار تصادم های هش (Hash Collisions) شناخته شده هستند و جداول از پیش محاسبه شده (Rainbow Tables) به راحتی آن ها را رمزگشایی می کنند. در سیستم های نوین باید از bcrypt یا PBKDF۲ با نمک تصادفی استفاده شود.
آیا Certificate Pinning ممکن است باعث قطعی اپلیکیشن کاربران شود؟
در صورتی که گواهینامه سرور تمدید شود و هش کلید عمومی تغییر کند، اگر توسعه دهنده کلید پشتیبان (Backup Pin) را از پیش در کلاینت تعبیه نکرده باشد، اپلیکیشن اجازه ارتباط با سرور را نخواهد داد. بنابراین ثبت پین های جایگزین الزامی است.
چگونه از نشت کلیدهای رمزنگاری در رم جلوگیری کنیم؟
به جای استفاده از نوع داده متنی String برای پسوردها و کلیدها، از آرایه های بایت (ByteArray) یا نویسه (CharArray) استفاده کنید تا بلافاصله پس از اتمام پردازش، بتوان مقادیر آن ها را در حافظه با عدد صفر بازنویسی کرد.
تفاوت ماژول امنیتی TEE با StrongBox چیست؟
محیط TEE یک بخش ایزوله در همان چیپست پردازنده اصلی (CPU) گوشی است، در حالی که StrongBox یک تراشه کاملا مستقل سخت افزاری با حافظه و پردازشگر اختصاصی ضد دستکاری است که در پرچمداران اندرویدی تعبیه شده است.
آیا می توان دیتابیس Room را به صورت کامل رمزنگاری کرد؟
بله، با استفاده از افزونه SQLCipher برای روم، کل فایل دیتابیس در سطح صفحات حافظه با الگوریتم ۲۵۶ بیتی AES رمزنگاری می شود و بدون رمز عبور یا کلید سخت افزاری امکان خواندن فایل دیتابیس وجود ندارد.
نقش وکتور اولیه (IV) در الگوریتم AES-GCM چیست؟
وکتور اولیه یک عدد تصادفی ۱۲ بایتی است که تضمین می کند اگر دو متن کاملا یکسان با یک کلید ثابت رمزنگاری شوند، خروجی های کاملا متفاوت و غیرقابل پیش بینی تولید شود تا امنیت سیستم در برابر تحلیل های آماری حفظ گردد.
امتیاز و نظرات
هنوز نظری ثبت نشده است. اولین نفر باشید.