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

آموزش رمزنگاری در اندروید

آموزش رمزنگاری در اندروید

آیا تاکنون به این فکر کرده‌اید که چگونه می‌توان با استفاده از رمزنگاری (Encryption)، اطلاعات خصوصی کاربران را در برابر هکرها و حملات سایبری محافظت کرد؟ در این مقاله، به‌صورت عملی با روش‌های ایمن‌سازی داده‌ها در اندروید آشنا خواهید شد.

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

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

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

  • محدود کردن و مدیریت مجوزهای برنامه (App Permissions)
  • رمزنگاری داده‌ها (Data Encryption)
  • استفاده از Android KeyStore برای نگهداری امن کلیدهای رمزنگاری

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

شروع پروژه

در ابتدای اجرای برنامه، یک صفحه ثبت‌نام (Sign Up) ساده مشاهده خواهید کرد. پس از وارد کردن رمز عبور و انتخاب گزینه Signup، در اجرای بعدی برنامه از شما خواسته می‌شود رمز عبور خود را وارد کنید.

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

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

صفحه لاگین کاربران

اگر در اندروید 7 و نسخه‌های بالاتر با خطای زیر مواجه شدید، نگران نباشید؛ به‌راحتی می‌توانید آن را برطرف کنید:

java.lang.SecurityException:
MODE_WORLD_READABLE no longer supported

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

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

در ادامه، ابتدا زیرساخت امنیتی برنامه را بهبود می‌دهیم تا بتوانید با خیال راحت فرآیند رمزنگاری اطلاعات خصوصی را آغاز کنید.

استفاده صحیح از Permission ها

هنگام طراحی و توسعه یک اپلیکیشن، همیشه از خود بپرسید:

«واقعاً چه مقدار از اطلاعات کاربران باید ذخیره شود؟»

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

از Android 6.0 به بعد، فایل‌ها و SharedPreferences به‌صورت پیش‌فرض با حالت MODE_PRIVATE ذخیره می‌شوند. این حالت تضمین می‌کند که فقط همان اپلیکیشن بتواند به داده‌ها دسترسی داشته باشد.

همچنین در Android 7 و نسخه‌های جدیدتر، استفاده از حالت‌های قدیمی مانند:

  • MODE_WORLD_READABLE
  • MODE_WORLD_WRITABLE

کاملاً ممنوع شده است؛ زیرا این حالت‌ها باعث می‌شدند سایر برنامه‌ها نیز بتوانند به فایل‌های شما دسترسی داشته باشند و این موضوع یک خطر امنیتی جدی محسوب می‌شود.

اصلاح کد برنامه

فایل MainActivity.kt را باز کنید. در این فایل مشاهده خواهید کرد که برای MODE_WORLD_READABLE و MODE_WORLD_WRITABLE هشدار Deprecated نمایش داده می‌شود.

ابتدا خطی که MODE_WORLD_WRITABLE را تنظیم کرده است پیدا کنید و آن را با کد زیر جایگزین نمایید:

val preferences = getSharedPreferences(
    "MyPrefs",
    Context.MODE_PRIVATE
)

سپس خط مربوط به MODE_WORLD_READABLE را پیدا کرده و آن را به شکل زیر تغییر دهید:

val editor = getSharedPreferences(
    "MyPrefs",
    Context.MODE_PRIVATE
).edit()

اکنون تنظیمات برنامه شما امنیت بیشتری دارد و داده‌ها تنها توسط خود اپلیکیشن قابل دسترسی خواهند بود.

همچنین اگر پروژه را مجدداً Build و Run کنید، دیگر با Crash قبلی مواجه نخواهید شد.

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

محدود کردن دایرکتوری نصب برنامه

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

این قابلیت در نگاه اول بسیار کاربردی بود؛ زیرا فضای بیشتری برای نصب اپلیکیشن‌ها فراهم می‌کرد. اما به مرور زمان، نگرانی‌های امنیتی زیادی درباره این روش به وجود آمد.

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

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

محدود کردن محل نصب برنامه

برای انجام این کار، فایل AndroidManifest.xml را باز کنید و خط زیر را پیدا نمایید:

android:installLocation="auto"

سپس مقدار آن را به internalOnly تغییر دهید:

android:installLocation="internalOnly"

با این کار، اپلیکیشن فقط روی حافظه داخلی دستگاه نصب خواهد شد و امکان نصب آن روی کارت حافظه خارجی وجود نخواهد داشت.

غیرفعال کردن قابلیت Backup

حتی با محدود کردن محل نصب برنامه، همچنان امکان تهیه نسخه پشتیبان (Backup) از داده‌های اپلیکیشن وجود دارد. به‌عنوان مثال، کاربران می‌توانند با استفاده از دستور adb backup به برخی از فایل‌های خصوصی برنامه دسترسی پیدا کنند.

برای جلوگیری از این موضوع، فایل AndroidManifest.xml را بررسی کرده و خط زیر را پیدا کنید:

android:allowBackup="true"

سپس مقدار آن را به false تغییر دهید:

android:allowBackup="false"

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

آیا این اقدامات کافی هستند؟

اقداماتی که تاکنون انجام داده‌اید، امنیت برنامه را تا حد زیادی افزایش می‌دهند؛ اما هنوز یک مشکل مهم وجود دارد.

در دستگاه‌های Root شده، برخی از این محدودیت‌ها قابل دور زدن هستند و مهاجم می‌تواند به داده‌های ذخیره‌شده دسترسی پیدا کند.

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

ایمن‌سازی اطلاعات کاربران با رمزنگاری

در این بخش، داده‌های کاربران را با استفاده از یکی از معروف‌ترین و امن‌ترین استانداردهای رمزنگاری یعنی استاندارد رمزنگاری پیشرفته (AES – Advanced Encryption Standard) محافظت خواهیم کرد.

الگوریتم AES یکی از پرکاربردترین روش‌های رمزنگاری در دنیا است و در بسیاری از سیستم‌های امنیتی، اپلیکیشن‌های بانکی و سرویس‌های بزرگ مورد استفاده قرار می‌گیرد.

AES با استفاده از ساختاری به نام شبکه جانشینی و جایگشت (Substitution-Permutation Network) داده‌ها را رمزنگاری می‌کند. در این روش، داده‌ها طی چندین مرحله تغییر، جابه‌جایی و جایگزینی بایت‌ها به شکلی تبدیل می‌شوند که بدون داشتن کلید رمزنگاری، قابل خواندن نباشند.

برای استفاده از AES، ابتدا باید یک کلید رمزنگاری (Encryption Key) ایجاد کنیم.

ایجاد کلید رمزنگاری

الگوریتم AES از یک کلید برای رمزنگاری داده‌ها استفاده می‌کند و همان کلید نیز برای رمزگشایی اطلاعات مورد نیاز است. به این نوع رمزنگاری، رمزنگاری متقارن (Symmetric Encryption) گفته می‌شود.

طول کلید در AES می‌تواند متفاوت باشد، اما امروزه کلید 256 بیتی یکی از استانداردهای رایج و بسیار امن محسوب می‌شود.

چرا نباید مستقیماً از رمز عبور کاربر استفاده کنیم؟

ممکن است تصور کنید که می‌توان رمز عبور کاربر را مستقیماً به‌عنوان کلید رمزنگاری استفاده کرد؛ اما این کار از نظر امنیتی بسیار خطرناک است.

زیرا:

  • بسیاری از کاربران رمزهای عبور ساده و قابل حدس انتخاب می‌کنند.
  • رمز عبور ممکن است طول کافی نداشته باشد.
  • احتمال تکراری بودن رمز عبور بین کاربران مختلف وجود دارد.

به همین دلیل، رمز عبور کاربر باید به یک کلید امن و استاندارد تبدیل شود.

استفاده از PBKDF2

برای حل این مشکل، از تابعی به نام:

PBKDF2 (Password-Based Key Derivation Function 2)

استفاده می‌شود.

این الگوریتم، رمز عبور کاربر را چندین بار Hash می‌کند و در کنار آن از داده‌ای تصادفی به نام Salt استفاده می‌کند تا در نهایت یک کلید رمزنگاری قدرتمند و منحصربه‌فرد تولید شود.

مزایای استفاده از PBKDF2:

  • افزایش امنیت کلید رمزنگاری
  • جلوگیری از حملات Dictionary و Brute Force
  • تولید کلیدهای متفاوت حتی برای رمزهای عبور مشابه
  • افزایش مقاومت در برابر حملات هکری

مفهوم Salt چیست؟

Salt یک مقدار تصادفی است که قبل از عملیات Hash به رمز عبور اضافه می‌شود. این کار باعث می‌شود حتی اگر دو کاربر رمز عبور یکسانی داشته باشند، کلیدهای رمزنگاری نهایی آن‌ها کاملاً متفاوت باشد.

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

ایمن سازی اطلاعات کاربر با رمز عبور

از آنجایی که هر کلید رمزنگاری به‌صورت منحصربه‌فرد تولید می‌شود، حتی اگر یک مهاجم (Attacker) موفق شود کلید یکی از کاربران را سرقت کرده و آن را منتشر کند، سایر کاربرانی که از رمز عبور مشابه استفاده کرده‌اند همچنان ایمن باقی می‌مانند. این موضوع یکی از مهم‌ترین مزایای استفاده از Salt و الگوریتم PBKDF2 است.

تولید Salt

ابتدا باید یک مقدار تصادفی (Salt) ایجاد کنید. فایل Encryption.kt را باز کرده و کد زیر را به‌جای عبارت TODO: Add code here در متد رمزنگاری اول قرار دهید:

val random = SecureRandom()

val salt = ByteArray(256)

random.nextBytes(salt)

در این بخش از کلاس SecureRandom استفاده می‌کنید. این کلاس یک تولیدکننده اعداد تصادفی رمزنگاری‌شده (Cryptographically Strong Random Number Generator) است و خروجی آن به‌گونه‌ای طراحی شده که پیش‌بینی آن برای مهاجمان بسیار دشوار باشد.

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

اکنون باید با استفاده از رمز عبور کاربر و مقدار Salt، یک کلید رمزنگاری تولید کنید.

کد زیر را بعد از بخش قبلی اضافه نمایید:

val pbKeySpec = PBEKeySpec(password, salt, 1324, 256)

val secretKeyFactory =
    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")

val keyBytes =
    secretKeyFactory.generateSecret(pbKeySpec).encoded

val keySpec = SecretKeySpec(keyBytes, "AES")

نکته مهم درباره رمز عبور

ممکن است توجه کرده باشید که بسیاری از توابع رمزنگاری به‌جای String از CharArray استفاده می‌کنند. دلیل این موضوع امنیت بیشتر است.

اشیاء String در جاوا و کاتلین تغییرناپذیر (Immutable) هستند و تا زمانی که Garbage Collector حافظه را آزاد نکند، در حافظه باقی می‌مانند. این موضوع می‌تواند یک خطر امنیتی باشد.

اما CharArray قابل بازنویسی (Overwrite) است و پس از پایان کار می‌توان محتویات آن را از حافظه پاک کرد تا اطلاعات حساس در حافظه باقی نمانند.

اضافه کردن بردار اولیه (Initialization Vector)

اکنون تقریباً همه‌چیز برای رمزنگاری داده‌ها آماده است، اما هنوز یک بخش مهم باقی مانده است: استفاده از بردار اولیه یا IV.

الگوریتم AES می‌تواند در حالت‌های مختلفی کار کند. یکی از رایج‌ترین حالت‌ها، حالت:

CBC (Cipher Block Chaining)

است.

در حالت CBC، داده‌ها به بلوک‌های کوچک تقسیم شده و هر بلوک قبل از رمزنگاری با بلوک رمزنگاری‌شده قبلی ترکیب (XOR) می‌شود. این فرآیند باعث می‌شود وابستگی بین بلوک‌ها افزایش یافته و امنیت رمزنگاری بیشتر شود.

اما یک مشکل وجود دارد.

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

برای حل این مشکل از:

Initialization Vector (IV)

استفاده می‌شود.

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

ساخت IV در اندروید

کد زیر را بعد از بخش تولید کلید اضافه کنید:

val ivRandom = SecureRandom()

// ایجاد آرایه 16 بایتی تصادفی
val iv = ByteArray(16)

ivRandom.nextBytes(iv)

// تبدیل IV به IvParameterSpec
val ivSpec = IvParameterSpec(iv)

چرا IV اهمیت دارد؟

بدون IV:

  • داده‌های مشابه خروجی مشابه تولید می‌کنند
  • الگوهای رمزنگاری قابل تشخیص می‌شوند
  • امنیت کاهش پیدا می‌کند

با IV:

  • هر بار خروجی متفاوت تولید می‌شود
  • حملات تحلیل الگو سخت‌تر می‌شود
  • امنیت CBC افزایش پیدا می‌کند

نکته مهم امنیتی

IV نیازی به مخفی بودن ندارد، اما باید:

  • تصادفی باشد
  • برای هر عملیات رمزنگاری جدید تولید شود
  • تکراری نباشد

معمولاً IV همراه داده رمزنگاری‌شده ذخیره یا ارسال می‌شود.

نکته درباره SecureRandom در اندروید

در نسخه‌های Android 4.3 و پایین‌تر، مشکلی امنیتی در پیاده‌سازی SecureRandom وجود داشت که به مقداردهی اولیه نامناسب تولیدکننده اعداد شبه‌تصادفی (PRNG) مربوط می‌شد.

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

رمزنگاری داده ها

رمزنگاری داده ها
انجام عملیات رمزنگاری داده‌ها

اکنون که تمام بخش‌های موردنیاز را آماده کرده‌اید:

  • کلید رمزنگاری (Key)
  • Salt
  • Initialization Vector (IV)

می‌توانید عملیات رمزنگاری را انجام دهید.

کد زیر را اضافه کنید:

val cipher =
    Cipher.getInstance(
        "AES/CBC/PKCS7Padding"
    )

cipher.init(
    Cipher.ENCRYPT_MODE,
    keySpec,
    ivSpec
)

val encrypted =
    cipher.doFinal(dataToEncrypt)

الگوریتم  AES

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

CBC

حالت:

Cipher Block Chaining

است.

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

PKCS7Padding

از آنجایی که AES داده‌ها را در بلوک‌های 128 بیتی پردازش می‌کند، ممکن است اندازه داده دقیقاً مضربی از اندازه بلوک نباشد.

در این حالت از Padding استفاده می‌شود.

PKCS7Padding فضای خالی لازم را به انتهای داده اضافه می‌کند تا اندازه داده با اندازه بلوک هماهنگ شود.

مقداردهی اولیه Cipher

cipher.init(
    Cipher.ENCRYPT_MODE,
    keySpec,
    ivSpec
)

در این قسمت:

  • حالت رمزنگاری فعال می‌شود
  • کلید رمزنگاری ارسال می‌شود
  • IV به Cipher داده می‌شود

انجام رمزنگاری

val encrypted =
    cipher.doFinal(dataToEncrypt)

متد doFinal() عملیات واقعی رمزنگاری را انجام داده و خروجی رمزنگاری‌شده را برمی‌گرداند.

خروجی معمولاً به‌صورت آرایه‌ای از بایت‌ها (ByteArray) است.

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

بعد از رمزنگاری، باید تمام اطلاعات لازم برای رمزگشایی را ذخیره کنید.

کد زیر را اضافه کنید:

map["salt"] = salt

map["iv"] = iv

map["encrypted"] = encrypted

چرا Salt و IV ذخیره می‌شوند؟

برای رمزگشایی داده‌ها، وجود این اطلاعات ضروری است:

  • Salt
  • Initialization Vector
  • داده رمزنگاری‌شده

بدون آن‌ها امکان بازگرداندن داده اصلی وجود ندارد.

نکته امنیتی مهم

ذخیره کردن:

  • Salt
  • IV

مشکلی ندارد، زیرا این داده‌ها محرمانه نیستند.

اما باید دقت کنید که:

  • IV تکراری نباشد
  • Salt مجدداً استفاده نشود
  • برای هر رمزنگاری جدید مقدار جدید تولید شود

هشدار مهم درباره کلید رمزنگاری

هرگز نباید کلید رمزنگاری را به‌صورت مستقیم ذخیره کنید.

ذخیره مستقیم Key می‌تواند امنیت کل سیستم را از بین ببرد.

بهتر است:

  • کلید از Password مشتق شود
  • از Android Keystore استفاده شود
  • یا کلید در محیط امن نگهداری شود

رمزگشایی داده ها

رمزگشایی داده ها

رمزگشایی داده‌ها (Decrypt)

اکنون شما داده‌های رمزنگاری‌شده در اختیار دارید. برای بازگرداندن داده‌ها به حالت اولیه، باید فرآیند رمزگشایی را انجام دهید.

در رمزگشایی تقریباً همان مراحل رمزنگاری تکرار می‌شوند، با این تفاوت که حالت Cipher از:

ENCRYPT_MODE

به:

DECRYPT_MODE

تغییر می‌کند.

کد زیر را داخل متد رمزگشایی در فایل:

Encryption.kt

در محل //TODO اضافه کنید:

val salt = map["salt"]

val iv = map["iv"]

val encrypted = map["encrypted"]

// بازسازی کلید از رمز عبور
val pbKeySpec =
    PBEKeySpec(
        password,
        salt,
        1324,
        256
    )

val secretKeyFactory =
    SecretKeyFactory.getInstance(
        "PBKDF2WithHmacSHA1"
    )

val keyBytes =
    secretKeyFactory
        .generateSecret(pbKeySpec)
        .encoded

val keySpec =
    SecretKeySpec(
        keyBytes,
        "AES"
    )

// رمزگشایی داده‌ها
val cipher =
    Cipher.getInstance(
        "AES/CBC/PKCS7Padding"
    )

val ivSpec =
    IvParameterSpec(iv)

cipher.init(
    Cipher.DECRYPT_MODE,
    keySpec,
    ivSpec
)

decrypted =
    cipher.doFinal(encrypted)

تنظیم حالت DECRYPT_MODE

cipher.init(
    Cipher.DECRYPT_MODE,
    keySpec,
    ivSpec
)

اینجا تفاوت اصلی با عملیات رمزنگاری مشخص می‌شود.

در این مرحله Cipher وارد حالت رمزگشایی می‌شود.

انجام عملیات رمزگشایی

decrypted =
    cipher.doFinal(encrypted)

متد doFinal() داده رمزنگاری‌شده را به حالت اولیه بازمی‌گرداند.

خروجی به‌صورت:

ByteArray

برگردانده می‌شود.

چرا این فرآیند کار می‌کند؟

شما از:

Symmetric Encryption

استفاده می‌کنید.

در رمزنگاری متقارن:

  • همان کلید برای Encrypt استفاده می‌شود
  • همان کلید برای Decrypt استفاده می‌شود

به همین دلیل بازسازی دقیق کلید اهمیت بسیار زیادی دارد.

هشدار امنیتی مهم

هرگز کلید رمزنگاری را ذخیره نکنید.

اگر مهاجم به کلید دسترسی داشته باشد، تمام داده‌های رمزنگاری‌شده قابل خواندن خواهند بود.

بهتر است:

  • کلید از Password مشتق شود
  • یا از Android Keystore استفاده شود

ذخیره داده‌های رمزنگاری‌شده

اکنون که فرآیند Encrypt و Decrypt کامل شده است، باید داده‌های رمزنگاری‌شده را در حافظه ذخیره کنید.

در فایل:

MainActivity.kt

متد:

createDataSource

را با کد زیر جایگزین کنید:

val inputStream =
    applicationContext.assets
        .open(filename)

val bytes =
    inputStream.readBytes()

inputStream.close()

val password =
    CharArray(login_password.length())

login_password.text.getChars(
    0,
    login_password.length(),
    password,
    0
)

val map =
    Encryption().encrypt(
        bytes,
        password
    )

ObjectOutputStream(
    FileOutputStream(outFile)
).use {

    it.writeObject(map)
}

چرا داده‌ها در برنامه نمایش داده نمی‌شوند؟

بعد از اجرای برنامه ممکن است لیست حیوانات خانگی خالی شود.

دلیل آن این است که داده‌ها اکنون رمزنگاری شده‌اند و برنامه هنوز بخش:

Decrypt هنگام خواندن فایل

را پیاده‌سازی نکرده است.

بنابراین داده‌ها دیگر به‌صورت متن ساده قابل خواندن نیستند.

ObjectOutputStream

خواندن داده‌های رمزنگاری‌شده

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

برای نمایش مجدد اطلاعات، باید هنگام خواندن فایل، داده‌ها را رمزگشایی کنید.

در فایل:

PetViewModel.kt

و داخل متد:

loadPets

ابتدا Comment های:

/*
*/

را حذف کنید.

سپس کد زیر را در محل:

// TODO: Add decrypt call here

اضافه کنید:

decrypted = Encryption().decrypt(

    hashMapOf(
        "iv" to iv,
        "salt" to salt,
        "encrypted" to encrypted
    ),

    password
)

inputStream

ایمن‌سازی SharedPreferences در اندروید

تا اینجا داده‌های فایل را با موفقیت رمزنگاری کردید، اما هنوز یک بخش مهم دیگر در برنامه باقی مانده است.

این برنامه آخرین زمان ورود کاربر را داخل:

SharedPreferences

ذخیره می‌کند.

نگهداری اطلاعات حساس در SharedPreferences به‌صورت ساده می‌تواند خطرناک باشد، زیرا حتی اگر از:

Context.MODE_PRIVATE

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

برای افزایش امنیت، باید داده‌ها را قبل از ذخیره‌سازی رمزنگاری کنید.

رمزنگاری داده قبل از ذخیره در SharedPreferences

فایل:

MainActivity.kt

را باز کنید و متد:

saveLastLoggedInTime

را با کد زیر جایگزین کنید:

// دریافت رمز عبور
val password =
    CharArray(login_password.length())

login_password.text.getChars(
    0,
    login_password.length(),
    password,
    0
)

// تبدیل تاریخ به String
val currentDateTimeString =
    DateFormat
        .getDateTimeInstance()
        .format(Date())

// رمزنگاری داده
val map =
    Encryption().encrypt(
        currentDateTimeString
            .toByteArray(Charsets.UTF_8),
        password
    )

// تبدیل داده‌ها به Base64
val valueBase64String =
    Base64.encodeToString(
        map["encrypted"],
        Base64.NO_WRAP
    )

val saltBase64String =
    Base64.encodeToString(
        map["salt"],
        Base64.NO_WRAP
    )

val ivBase64String =
    Base64.encodeToString(
        map["iv"],
        Base64.NO_WRAP
    )

// ذخیره در SharedPreferences
val editor =
    getSharedPreferences(
        "MyPrefs",
        Context.MODE_PRIVATE
    ).edit()

editor.putString(
    "l",
    valueBase64String
)

editor.putString(
    "lsalt",
    saltBase64String
)

editor.putString(
    "liv",
    ivBase64String
)

editor.apply()

ذخیره داده‌ها در SharedPreferences

editor.putString(...)

اکنون مقادیر رمزنگاری‌شده ذخیره می‌شوند.

چرا Salt و IV ذخیره می‌شوند؟

برای رمزگشایی داده‌ها وجود این مقادیر ضروری است:

  • Salt
  • IV
  • داده رمزنگاری‌شده

بدون آن‌ها امکان بازیابی داده اصلی وجود ندارد.

افزایش امنیت کلیدهای SharedPreferences

در این مثال حتی نام کلیدها نیز مبهم انتخاب شده‌اند:

l
lsalt
liv

اگر از کلیدهای واضحی مثل:

password
token
secret

استفاده کنید، مهاجم راحت‌تر می‌تواند هدف داده‌ها را تشخیص دهد.

در پروژه‌های حساس حتی می‌توانید نام کلیدها را نیز رمزنگاری کنید.

خواندن و رمزگشایی داده‌ها

اکنون باید هنگام خواندن اطلاعات از SharedPreferences داده‌ها را رمزگشایی کنید.

متد:

lastLoggedIn

را با کد زیر جایگزین کنید:

// دریافت رمز عبور
val password =
    CharArray(login_password.length())

login_password.text.getChars(
    0,
    login_password.length(),
    password,
    0
)

// خواندن داده‌ها از SharedPreferences
val preferences =
    getSharedPreferences(
        "MyPrefs",
        Context.MODE_PRIVATE
    )

val base64Encrypted =
    preferences.getString("l", "")

val base64Salt =
    preferences.getString("lsalt", "")

val base64Iv =
    preferences.getString("liv", "")

// تبدیل Base64 به ByteArray
val encrypted =
    Base64.decode(
        base64Encrypted,
        Base64.NO_WRAP
    )

val iv =
    Base64.decode(
        base64Iv,
        Base64.NO_WRAP
    )

val salt =
    Base64.decode(
        base64Salt,
        Base64.NO_WRAP
    )

// رمزگشایی داده‌ها
val decrypted =
    Encryption().decrypt(

        hashMapOf(
            "iv" to iv,
            "salt" to salt,
            "encrypted" to encrypted
        ),

        password
    )

HashMap

استفاده از کلید از طریق سرور

نمایش یک صفحه‌ی جداگانه برای وارد کردن رمز عبور، علاوه بر صفحه‌ی ورود (Login)، معمولاً تجربه کاربری (UX) مناسبی ایجاد نمی‌کند. برای چنین سناریوهایی چند راه‌حل مختلف وجود دارد.

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

اگر از رویکرد مبتنی بر سرور استفاده می‌کنید، باید توجه داشته باشید که چون کلید توسط سرور تولید می‌شود، سرور نیز توانایی رمزگشایی داده‌های ذخیره‌شده روی دستگاه را خواهد داشت. در نتیجه، در صورت نفوذ یا compromise شدن سرور، امکان دسترسی به کلیدها و داده‌ها وجود دارد.

اگر هیچ‌یک از این راه‌حل‌ها مناسب نیاز شما نیست، می‌توانید از قابلیت‌های امنیتی خود دستگاه برای محافظت از برنامه استفاده کنید.

استفاده از KeyStore

از نسخه Android M، اندروید امکان کار با کلیدهای AES را از طریق KeyStore API فراهم کرده است. این روش مزایای امنیتی مهمی دارد. KeyStore به شما اجازه می‌دهد بدون افشای محتوای واقعی کلید، عملیات رمزنگاری را انجام دهید. در این حالت، تنها مرجع (Reference) کلید در اختیار برنامه قرار می‌گیرد و داده‌ی محرمانه‌ی کلید هرگز از فضای امن سیستم خارج نمی‌شود.

ایجاد یک کلید تصادفی جدید

در فایل Encryption.kt، کد زیر را به متد keystoreTest اضافه کنید تا یک کلید تصادفی ایجاد و در KeyStore اندروید ذخیره شود. در این روش، کلید به‌صورت امن توسط KeyStore محافظت می‌شود:

val keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES,
    "AndroidKeyStore"
)

val keyGenParameterSpec = KeyGenParameterSpec.Builder(
    "MyKeyAlias",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)

    // نیاز به احراز هویت کاربر دارد.
    // در صورت غیرفعال شدن قفل صفحه، کلید نامعتبر می‌شود.
    //.setUserAuthenticationRequired(true)

    // کلید فقط تا چند ثانیه پس از احراز هویت قابل استفاده است.
    // مقدار -1 یعنی در هر بار استفاده، احراز هویت (مانند اثر انگشت) الزامی است.
    //.setUserAuthenticationValidityDurationSeconds(120)

    // برای یک متن یکسان، در هر بار رمزنگاری خروجی متفاوتی تولید می‌کند.
    .setRandomizedEncryptionRequired(true)
    .build()

keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()

در این مثال، یک کلید AES با نام مستعار MyKeyAlias تولید می‌شود که برای عملیات رمزنگاری و رمزگشایی قابل استفاده است. همچنین با استفاده از حالت GCM امنیت بالاتری برای رمزنگاری فراهم می‌شود.

متد decrypt

از اینجا کجا بریم ؟

تبریک می گوییم، شما روش های رمزنگاری و رمزگشایی داده ها را در اندروید یاد گرفته اید!

رمزنگاری و رمزگشایی داده ها

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

دانستن نحوه صحیح پیاده‌سازی امنیت اهمیت زیادی دارد. با این دانش می‌توانید بررسی کنید که آیا کتابخانه‌های امنیتی شخص ثالث (Third-Party) از بهترین شیوه‌های امنیتی پیروی می‌کنند یا خیر. با این حال، پیاده‌سازی کامل همه‌چیز به‌صورت دستی، مخصوصاً زمانی که محدودیت زمانی دارید، ممکن است منجر به بروز اشتباهات امنیتی شود.

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

Account Manager

Account Manager بخشی از سیستم‌عامل اندروید است و API اختصاصی خود را دارد. این سرویس به‌عنوان یک مدیر متمرکز برای اطلاعات حساب‌های کاربری عمل می‌کند؛ بنابراین برنامه شما نیازی ندارد مستقیماً رمز عبور یا اطلاعات ورود را ذخیره و مدیریت کند. یکی از رایج‌ترین کاربردهای آن، دریافت توکن‌های OAuth2 است.

KeyChain API

KeyChain API که از اندروید 4.0 (API Level 14) معرفی شد، برای مدیریت کلیدها طراحی شده و به‌طور ویژه با اشیای PrivateKey و گواهی‌های X509 Certificate کار می‌کند. این API نسبت به ذخیره‌سازی اطلاعات در فضای داخلی برنامه، محیط امن‌تری برای نگهداری کلیدها فراهم می‌کند. همچنین می‌توانید از آن برای نصب گواهی‌ها و استفاده مستقیم از کلید خصوصی بهره ببرید.

محافظت از کدهای امنیتی

کدهای امنیتی برنامه تا زمانی مؤثر هستند که مورد دستکاری قرار نگیرند. برای جلوگیری از مهندسی معکوس (Reverse Engineering) و محافظت از بخش‌های حساس برنامه، می‌توانید از ProGuard استفاده کنید. این ابزار با مبهم‌سازی (Obfuscation) کد، تحلیل و تغییر کدهای مرتبط با امنیت را دشوارتر می‌کند.

نتیجه‌گیری

امنیت در برنامه‌های اندرویدی تنها به رمزنگاری داده‌ها محدود نمی‌شود، بلکه شامل مدیریت صحیح کلیدها، محافظت از اطلاعات کاربری و جلوگیری از دستکاری کد نیز هست. استفاده از ابزارهایی مانند KeyStore، KeyChain API و Account Manager به شما کمک می‌کند تا اطلاعات حساس را با استانداردهای امن‌تری مدیریت کنید و وابستگی کمتری به ذخیره‌سازی مستقیم داده‌های محرمانه داشته باشید.

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

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

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