EncryptedSharedPreferences — مؤلفهای از کتابخانه AndroidX Security است که رمزنگاری شفاف دادههای ذخیره شده از طریق API SharedPreferences را فراهم میکند. برخلاف SharedPreferences معمولی که دادهها در یک فایل XML باز ذخیره میشوند، EncryptedSharedPreferences به طور خودکار کلیدها و مقادیر را قبل از نوشتن روی دیسک رمزنگاری میکند. طبق Android Developers، کتابخانه از AES-256 GCM برای مقادیر و AES-256 SIV (RFC 5297) برای کلیدها استفاده میکند و محرمانگی و یکپارچگی دادهها را تضمین میکند.
خلاصه
EncryptedSharedPreferences — کلاسی از بسته androidx.security.crypto است که در AndroidX Security 1.0.0 (2019) معرفی شد. این کلاس رابط SharedPreferences را پیادهسازی میکند، اما تمام عملیات نوشتن (putString, putInt, putBoolean و غیره) دادهها را از قبل رمزنگاری میکنند و عملیات خواندن قبل از بازگرداندن آنها را رمزگشایی میکنند.
SharedPreferences استاندارد دادهها را در یک فایل XML در دایرکتوری برنامه ذخیره میکند (/data/data/package/shared_prefs/). فایل رمزنگاری نشده است — با دسترسی root به دستگاه یا تحلیل پشتیبان، تمام دادهها به صورت XML ساده قابل خواندن هستند. توکنهای احراز هویت، کلیدهای API، اطلاعات شخصی کاربر برای مهاجم قابل دسترس میشود.
EncryptedSharedPreferences این مشکل را در سطح کتابخانه حل میکند: دادهها قبل از نوشتن روی دیسک رمزنگاری میشوند و هنگام خواندن رمزگشایی میشوند. توسعهدهنده نیازی به فراخوانی دستی توابع رمزنگاری ندارد — API مشابه SharedPreferences معمولی باقی میماند.
کتابخانه AndroidX Security v1.0.0 در دسامبر 2019 منتشر شد. EncryptedSharedPreferences جایگزین رویکرد قدیمی با رمزنگاری دستی از طریق Cipher + SharedPreferences شد. نسخه پایدار فعلی 1.1.0-alpha06 (2024) است که از API 19+ پشتیبانی میکند. کتابخانه بخشی از Jetpack است و به مجوزهای اضافی نیاز ندارد.
به گفته Google Security Blog (2024)، EncryptedSharedPreferences روش توصیه شده برای ذخیره تنظیمات محرمانه برنامه است که نیازی به همگامسازی از طریق ابر ندارند. برای سناریوهای پیچیدهتر، Room با رمزنگاری از طریق SQLCipher پیشنهاد میشود.
EncryptedSharedPreferences از طرح رمزنگاری دو سطحی استفاده میکند: کلید اصلی (Master Key) در Android Keystore ذخیره میشود و برای رمزنگاری دادهها از کلیدهای مشتق شده استفاده میشود. این ترکیبی از حفاظت Keystore و کارایی رمزنگاری متقارن است.
برای مقادیر از AES-256 GCM (Galois/Counter Mode) استفاده میشود — حالت رمزنگاری تأیید شده (AEAD) که محرمانگی و یکپارچگی دادهها را تضمین میکند. برای کلیدها (نام پارامترها) AES-256 SIV (RFC 5297) اعمال میشود — رمزنگاری قطعی که برای جستجو بر اساس کلید بدون افشای محتوای آن ضروری است.
هر فایل EncryptedSharedPreferences شامل جفتهای کلید-مقدار رمزنگاری شده است. ساختار فایل: ابتدا هدر با فراداده (نسخه، شناسه کلید)، سپس لیستی از ورودیهای رمزنگاری شده. فایل XML معتبر نیست و با ویرایشگرهای متن قابل خواندن نیست.
کلاس MasterKey مسئول ایجاد و مدیریت کلید اصلی 256 بیتی است که در Android Keystore ذخیره میشود. MasterKey.Builder امکان پیکربندی: نوع ذخیرهسازی (Keystore یا نرمافزاری)، حفاظت با بیومتریک، عمر کلید را فراهم میکند. به طور پیشفرض، کلید اصلی در Android Keystore با الگوریتم AES/GCM/NoPadding تولید میشود.
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences
fun getEncryptedPrefs() {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
EncryptedSharedPreferences.create پنج پارامتر دریافت میکند: زمینه، نام فایل، کلید اصلی، طرح رمزنگاری کلیدها و طرح رمزنگاری مقادیر. انتخاب طرحها بر عملکرد و سطح حفاظت تأثیر میگذارد.
AES256_SIV — رمزنگاری قطعی: کلیدهای مشابه همیشه متن رمزنگاری شده مشابهی تولید میکنند. این برای جستجو بر اساس کلید (SharedPreferences.getX(key)) ضروری است. عیب: مهاجم میتواند تشخیص دهد کدام کلیدها با تکرار متنهای رمزی استفاده میشوند. AES256_SIV2 — نسخه بهبود یافته با تصادفیسازی اضافی.
برای مقادیر از AES256_GCM استفاده میشود. GCM یک IV 12 بایتی (بردار اولیه) و یک برچسب تأیید 16 بایتی به هر مقدار اضافه میکند. این محرمانگی (هیچکس نمیتواند مقدار را بخواند) و تأیید (هیچکس نمیتواند مقدار را بدون شناسایی تغییر دهد) را تضمین میکند.
روش setUserAuthenticationRequired(true) در MasterKey.Builder قبل از دریافت کلید اصلی از Keystore تأیید بیومتریک را الزامی میکند. این یک لایه اضافی اضافه میکند: حتی اگر برنامه روی دستگاه باز شده اجرا شود، مهاجم نمیتواند EncryptedSharedPreferences را بدون Face ID یا Touch ID بخواند.
مهم: با setUserAuthenticationRequired، کلید اصلی در صورت تغییر یا حذف بیومتریک توسط کاربر غیرقابل دسترس میشود. باید KeyPermanentlyInvalidatedException را مدیریت کرده و با مهاجرت دادهها یک کلید اصلی جدید ایجاد کنید.
fun createBiometricKey(): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.setUserAuthenticationRequired(true)
.setRequestStrongBoxBacked(true)
.build()
}
fun writeSecureToken(token: String) {
try {
prefs.edit().putString("auth_token", token).apply()
} catch (e: KeyPermanentlyInvalidatedException) {
// بیومتریک تغییر کرد — باید کلید را دوباره ایجاد کنید
}
}
بیایید یک مثال کامل از ادغام EncryptedSharedPreferences در یک برنامه اندروید با کاتلین را بررسی کنیم. کتابخانه androidx.security:security-crypto از طریق Gradle اضافه میشود.
در فایل build.gradle (app) اضافه کنید: implementation «androidx.security:security-crypto:1.1.0-alpha06». برای پروژههای کاتلین، kotlin-stdlib نیز مورد نیاز است. مقداردهی اولیه MasterKey یک بار، معمولاً در Application.onCreate یا از طریق کانتینر DI انجام میشود.
پس از ایجاد نمونه EncryptedSharedPreferences، API با SharedPreferences معمولی تفاوتی ندارد. edit() یک Editor برمیگرداند، همه روشها (putString, getString, putBoolean, getBoolean) مشابه عمل میکنند. تفاوت فقط در داخل است: دادهها هنگام نوشتن رمزنگاری و هنگام خواندن رمزگشایی میشوند.
class AuthRepository(context: Context) {
private val prefs = createEncryptedPrefs(context)
fun saveCredentials(login: String, password: String) {
prefs.edit()
.putString("login", login)
.putString("password", password)
.apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
برای مهاجرت دادههای موجود از SharedPreferences محافظت نشده به EncryptedSharedPreferences باید: تمام دادهها را از فایل قدیمی بخوانید، یک EncryptedSharedPreferences جدید ایجاد کنید، تمام دادهها را بنویسید، فایل قدیمی را حذف کنید. گوگل ابزار مهاجرت داخلی ارائه نمیدهد — توسعهدهنده آن را خودش پیادهسازی میکند.
انتخاب بین SharedPreferences و EncryptedSharedPreferences به نوع دادههای ذخیره شده بستگی دارد. برای تنظیمات رابط کاربری (پوسته، زبان، مرتبسازی) SharedPreferences معمولی کافی است. برای اطلاعات محرمانه (توکنها، رمزهای عبور، کلیدها) EncryptedSharedPreferences الزامی است.
EncryptedSharedPreferences به دلیل عملیات رمزنگاری کندتر از نوع معمولی است. نوشتن یک مقدار رشتهای حدود 5-15 میلیثانیه طول میکشد (بسته به اندازه داده و شتاب سختافزاری AES). خواندن — 2-5 میلیثانیه. برای اکثر برنامهها این قابل توجه نیست، اما در عملیات دستهای (مهاجرت، بازیابی) بهتر است به جای commit() از apply() استفاده کنید.
SharedPreferences معمولی هیچ حفاظت رمزنگاری ارائه نمیدهد: فایل XML توسط هر فرآیندی با دسترسی root یا از طریق adb backup قابل خواندن است. EncryptedSharedPreferences دادهها را در سطح برنامه رمزنگاری میکند و کلید اصلی در Android Keystore با امکان حفاظت سختافزاری (StrongBox) ذخیره میشود.
| ویژگی | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| ذخیرهسازی | XML باز | فایل باینری رمزنگاری شده |
| رمزنگاری | ندارد | AES-256 GCM + SIV |
| حفاظت کلیدها | ندارد | Android Keystore + StrongBox |
| عملکرد | 0.1-1 میلیثانیه | 2-15 میلیثانیه |
| توصیه | تنظیمات UI | توکنها، کلیدها، PII |
از EncryptedSharedPreferences برای ذخیره استفاده کنید: توکن بازخوانی OAuth، کلیدهای API برای سرویسهای خارجی، ایمیل یا شماره تلفن کاربر، تنظیمات محرمانه برنامه (PIN، پرچمهای احراز هویت). برای ذخیره دادههای بیومتریک یا اسناد بزرگ، EncryptedSharedPreferences مناسب نیست — از EncryptedFile یا Room با SQLCipher استفاده کنید.
قاعده کلی: اگر نشت داده به کاربر یا کسبوکار آسیب میرساند — از EncryptedSharedPreferences استفاده کنید. اگر دادهها فقط آرایشی هستند (پوسته، زبان، مرتبسازی) — SharedPreferences معمولی. پیادهسازی EncryptedSharedPreferences از همان ابتدا منطقی است، بدون بازسازی: جایگزینی در پروژه موجود نیاز به مهاجرت و پردازش دادههای قدیمی رمزنگاری نشده دارد.
به یاد داشته باشید که EncryptedSharedPreferences دادهها را فقط روی دیسک محافظت میکند — نه در حین کار برنامه. اگر مهاجم به حافظه فرآیند دسترسی داشته باشد، دادههای رمزگشایی شده ممکن است رهگیری شوند. از حفاظت اضافی استفاده کنید: ProGuard/DexGuard برای مبهمسازی کد.
سوالات متداول
Jetpack DataStore — جایگزین مدرنتری برای SharedPreferences است که بر اساس Flow و کوروتینهای کاتلین ساخته شده است. DataStore به طور پیشفرض دادهها را رمزنگاری نمیکند، اما میتواند با EncryptedSharedPreferences ترکیب شود یا با رمزنگاری دستی از طریق Proto DataStore با پروتکلهای رمزنگاری استفاده شود.
توصیه نمیشود. EncryptedSharedPreferences برای حجمهای کم (تا 100-200 کیلوبایت) طراحی شده است. برای دادههای بزرگ از Room با SQLCipher یا رمزنگاری فایل از طریق EncryptedFile از همان کتابخانه AndroidX Security استفاده کنید.
خیر، مهاجرت خودکار طرح وجود ندارد. هنگام تغییر ساختار داده، توسعهدهنده باید دادههای قدیمی را از طریق KeyGen قدیمی به صورت دستی بخواند و از طریق جدید بنویسد. توصیه میشود نسخه طرح را در یک پارامتر جداگانه ذخیره کنید.
AndroidX Security 1.0.0 از API 19+ (Android KitKat) پشتیبانی میکند. نسخه 1.1.0-alpha06 نیز از API 19+ پشتیبانی میکند. برای StrongBox به API 28+ و دستگاه با پشتیبانی سختافزاری نیاز است (Google Pixel 3+, Samsung Galaxy S9+).
بله، توکن بازخوانی — یکی از سناریوهای اصلی استفاده است. رمزنگاری AES-256 GCM، کلید اصلی در Keystore، حفاظت بیومتریک — سطح کافی برای توکنهای OAuth. برای توکن دسترسی با عمر کوتاه نیز مناسب است، اگرچه برخی تیمها ترجیح میدهند آن را در حافظه ذخیره کنند.
نتیجهگیری
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید