سیستم Android Keystore

سیستم Android Keystore به شما امکان می‌دهد کلیدهای رمزنگاری‌شده را در محفظه‌ای ذخیره کنید تا استخراج آن‌ها از دستگاه دشوارتر شود. وقتی کلیدها در keystore قرار گرفتند، می‌توانید از آن‌ها برای عملیات رمزنگاری استفاده کنید، درحالی‌که مواد کلید غیرقابل‌صادر کردن باقی می‌مانند. همچنین، سیستم keystore به شما امکان می‌دهد زمان و نحوه استفاده از کلیدها را محدود کنید، مثلاً برای استفاده از کلید، اصالت‌سنجی کاربر را الزامی کنید یا کلیدها را فقط برای استفاده در حالت‌های رمزنگاری خاص محدود کنید. برای اطلاعات بیشتر، بخش ویژگی‌های امنیتی را ببینید.

سیستم keystore توسط KeyChain API، معرفی‌شده در Android 4.0 (سطح API‏ ۱۴)، و همچنین ویژگی ارائه‌دهنده Android Keystore، معرفی‌شده در Android 4.3 (سطح API‏ ۱۸)، استفاده می‌شود. این سند توضیح می‌دهد که چه زمانی و چگونه از سیستم Android Keystore استفاده کنید.

ویژگی‌های امنیتی

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

جلوگیری از استخراج

محتوای کلیدهای Android Keystore بااستفاده از دو اقدام امنیتی دربرابر استخراج محافظت می‌شود:

  • محتوای کلید هرگز وارد فرایند برنامه نمی‌شود. وقتی برنامه‌ای بااستفاده از کلید Android Keystore عملیات رمزنگاری انجام می‌دهد، در پشت صحنه، متن ساده، متن رمزی، و پیام‌هایی که باید امضا یا تأیید شوند به فرایند سیستمی که عملیات رمزنگاری را انجام می‌دهد ارسال می‌شوند. اگر فرایند برنامه به‌خطر بیفتد، مهاجم ممکن است بتواند از کلیدهای برنامه استفاده کند اما نمی‌تواند مواد کلید را استخراج کند (برای مثال، برای استفاده در خارج از دستگاه Android).
  • محتوای کلید می‌تواند به سخت‌افزار امن دستگاه Android، مانند محیط اجرای امن (TEE) یا عنصر امن (SE)، متصل شود. وقتی این ویژگی برای کلیدی فعال باشد، داده‌های کلید هرگز خارج از سخت‌افزار ایمن افشا نمی‌شود. اگر سیستم‌عامل Android به‌خطر بیفتد یا مهاجم بتواند حافظه داخلی دستگاه را بخواند، مهاجم ممکن است بتواند از کلیدهای Android Keystore هر برنامه‌ای در دستگاه Android استفاده کند، اما نمی‌تواند آن‌ها را از دستگاه استخراج کند. این ویژگی فقط درصورتی فعال می‌شود که سخت‌افزار ایمن دستگاه از ترکیب خاص الگوریتم کلید، حالت‌های بلوک، طرح‌های لایه‌گذاری، و خلاصه‌هایی که کلید مجاز به استفاده با آن‌ها است پشتیبانی کند.

    برای بررسی اینکه آیا این ویژگی برای کلیدی فعال است یا نه، KeyInfo را برای آن کلید دریافت کنید. مرحله بعدی به نسخه کیت توسعه نرم‌افزار هدف برنامه‌تان بستگی دارد:

    • اگر برنامه شما Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) یا بالاتر را هدف قرار می‌دهد، مقدار بازگشتی getSecurityLevel() را بررسی کنید. مقادیر برگشتی منطبق با KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT یا KeyProperties.SecurityLevelEnum.STRONGBOX نشان می‌دهد که کلید در سخت‌افزار امن قرار دارد.
    • اگر برنامه شما Android 9 (سطح ای‌پی‌آی ۲۸) یا پایین‌تر را هدف قرار می‌دهد، مقدار بازگشتی بولی KeyInfo.isInsideSecurityHardware() را بررسی کنید.

عنصر امنیتی StrongBox KeyMint

دستگاه‌های دارای Android 9 (سطح API‏ ۲۸) یا بالاتر می‌توانند شامل StrongBox KeyMint باشند، پیاده‌سازی KeyMint HAL که توسط StrongBox پشتیبانی می‌شود. درحالی‌که پودمان‌های امنیتی سخت‌افزاری (HSM) به‌طور کلی به راه‌حل‌های ذخیره‌سازی کلید امن اشاره دارند که دربرابر آسیب‌پذیری‌های هسته Linux مقاوم هستند، StrongBox به‌طور خاص به پیاده‌سازی‌ها در SEهای جاسازی‌شده یا «پناهگاه‌های امن» یکپارچه (iSE) اشاره دارد که جداسازی قوی‌تر و مقاومت بیشتری دربرابر دستکاری نسبت‌به TEE ارائه می‌دهد.

پیاده‌سازی StrongBox KeyMint باید شامل موارد زیر باشد:

  • واحد پردازش مرکزی خودش
  • فضای ذخیره‌سازی امن
  • تولیدکننده عدد تصادفی واقعی
  • سازوکارهای اضافی برای مقاومت دربرابر دست‌کاری بسته و جانبی‌گذاری غیرمجاز برنامه‌ها
  • زمان‌سنج ایمن
  • پین اعلان راه‌اندازی مجدد (یا معادل آن)، مانند ورودی/خروجی همه‌منظوره (GPIO)

زیرمجموعه‌ای از الگوریتم‌ها و اندازه‌های کلید برای تطبیق با پیاده‌سازی‌های StrongBox کم‌مصرف پشتیبانی می‌شوند:

  • RSA 2048
  • AES 128 و 256
  • ECDSA،‏ ECDH P-256
  • HMAC-SHA256 (از اندازه‌های کلید بین ۸ بایت و ۶۴ بایت، شامل هر دو، پشتیبانی می‌کند)
  • استاندارد رمزگذاری داده‌های سه‌گانه
  • APDU با طول گسترش‌یافته

‫StrongBox از تصدیق کلید نیز پشتیبانی می‌کند.

استفاده از StrongBox KeyMint

از FEATURE_STRONGBOX_KEYSTORE برای بررسی اینکه آیا StrongBox در دستگاه دردسترس است یا نه استفاده کنید. اگر StrongBox دردسترس باشد، می‌توانید با ارسال true به روش‌های زیر، اولویت خود را برای ذخیره کردن کلید در StrongBox KeyMint نشان دهید:

اگر StrongBox KeyMint از الگوریتم یا اندازه کلید مشخص‌شده پشتیبانی نکند، چارچوب StrongBoxUnavailableException را برمی‌گرداند. اگر این اتفاق افتاد، کلید را بدون فراخوانی setIsStrongBoxBacked(true) تولید یا وارد کنید.

مجوزهای استفاده از کلید

برای جلوگیری از استفاده غیرمجاز از کلیدها در دستگاه Android، «فروشگاه کلید Android» به برنامه‌ها اجازه می‌دهد تا هنگام تولید یا وارد کردن کلیدها، استفاده‌های مجاز از کلیدهایشان را مشخص کنند. وقتی کلیدی تولید یا وارد شود، مجوزهای آن را نمی‌توان تغییر داد. سپس هرگاه از کلید استفاده شود، مجوزها توسط Android Keystore اعمال می‌شود. این یک ویژگی امنیتی پیشرفته است که معمولاً فقط درصورتی مفید است که الزامات شما این باشد که به‌خطر افتادن فرایند برنامه‌تان پس‌از تولید/وارد کردن کلید (اما نه قبل یا درطول آن) نتواند منجر به استفاده غیرمجاز از کلید شود.

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

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

به‌عنوان یک اقدام امنیتی اضافی برای کلیدهایی که مواد کلید آن‌ها داخل سخت‌افزار ایمن است (ببینید KeyInfo.isInsideSecurityHardware() یا، برای برنامه‌هایی که Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹) یا بالاتر را هدف قرار می‌دهند، KeyInfo.getSecurityLevel())، برخی‌از مجوزهای استفاده از کلید ممکن است توسط سخت‌افزار ایمن اعمال شوند، بسته به دستگاه Android. سخت‌افزار ایمن معمولاً مجوزهای اصالت‌سنجی کاربر و رمزنگاری را اعمال می‌کند. اما سخت‌افزار ایمن معمولاً مجوزهای بازه اعتبار زمانی را اعمال نمی‌کند، زیرا معمولاً ساعت بی‌درنگ مستقل و ایمن ندارد.

می‌توانید بااستفاده از KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() پرس‌وجو کنید که آیا مجوز اصالت‌سنجی کاربر کلید توسط سخت‌افزار ایمن اجرا می‌شود یا نه.

بین keychain و ارائه‌دهنده Android Keystore یکی را انتخاب کنید

وقتی اعتبارنامه‌های سراسری سیستم را می‌خواهید، از KeyChain API استفاده کنید. وقتی برنامه‌ای درخواست استفاده از هرگونه اطلاعات کاربری را ازطریق KeyChain API می‌کند، کاربران می‌توانند ازطریق واسط کاربر ارائه‌شده توسط سیستم انتخاب کنند که برنامه به کدام‌یک از اطلاعات کاربری نصب‌شده دسترسی داشته باشد. این ویژگی به چندین برنامه اجازه می‌دهد با رضایت کاربر از مجموعه یکسانی از اطلاعات کاربری استفاده کنند.

از ارائه‌دهنده Android Keystore استفاده کنید تا به برنامه‌ای خاص اجازه دهید اطلاعات کاربری خودش را ذخیره کند، اطلاعاتی که فقط آن برنامه می‌تواند به آن‌ها دسترسی داشته باشد. این روشی را برای برنامه‌ها فراهم می‌کند تا اطلاعات اعتباری را که فقط خودشان می‌توانند استفاده کنند مدیریت کنند و درعین‌حال همان مزایای امنیتی را که KeyChain API برای اطلاعات اعتباری سراسر سیستم ارائه می‌دهد فراهم می‌کند. این روش نیازی به انتخاب اعتبارنامه توسط کاربر ندارد.

استفاده از ارائه‌دهنده Android Keystore

برای استفاده از این ویژگی، از کلاس‌های استاندارد KeyStore و KeyPairGenerator یا KeyGenerator به‌همراه ارائه‌دهنده AndroidKeyStore معرفی‌شده در Android 4.3 (سطح API‏ ۱۸) استفاده می‌کنید.

‫AndroidKeyStore به‌عنوان نوع KeyStore برای استفاده با روش KeyStore.getInstance(type) و به‌عنوان ارائه‌دهنده برای استفاده با روش‌های KeyPairGenerator.getInstance(algorithm, provider) و KeyGenerator.getInstance(algorithm, provider) ثبت شده است.

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

تولید کلید خصوصی یا رمز جدید

برای تولید KeyPair جدید حاوی PrivateKey، باید مشخصه‌های اولیه X.509 گواهی را مشخص کنید. می‌توانید از KeyStore.setKeyEntry() استفاده کنید تا بعداً گواهی را با گواهی امضاشده توسط مرجع صدور گواهی (CA) جایگزین کنید.

برای تولید جفت کلید، از KeyPairGenerator با KeyGenParameterSpec استفاده کنید:

کاتلین

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

جاوا

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

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

Android نسخه ۹ (سطح میانای برنامه‌سازی کاربردی ۲۸) و بالاتر به شما امکان می‌دهد کلیدهای رمزگذاری‌شده را بااستفاده از قالب کلید کدگذاری‌شده ASN.1 به‌طور ایمن به keystore وارد کنید. سپس Keymaster کلیدها را در keystore رمزگشایی می‌کند، بنابراین محتوای کلیدها هرگز به‌صورت متن ساده در حافظه میزبان دستگاه نشان داده نمی‌شود. این فرایند امنیت رمزگشایی کلید اضافی را فراهم می‌کند.

برای پشتیبانی از وارد کردن ایمن کلیدهای رمزگذاری‌شده به keystore، مراحل زیر را تکمیل کنید:

  1. جفت کلیدی ایجاد کنید که از هدف PURPOSE_WRAP_KEY استفاده می‌کند. توصیه می‌کنیم گواهی را به این جفت کلید نیز اضافه کنید.

  2. در سرور یا دستگاهی که به آن اعتماد دارید، پیام ASN.1 را برای SecureKeyWrapper تولید کنید.

    پوشاننده حاوی طرح‌واره زیر است:

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. یک شیء WrappedKeyEntry ایجاد کنید و پیام ASN.1 را به‌عنوان آرایه بایت ارسال کنید.

  4. این شیء WrappedKeyEntry را به سربارگذاری setEntry() که شیء Keystore.Entry را می‌پذیرد منتقل کنید.

کار کردن با ورودی‌های keystore

می‌توانید ازطریق همه میاناهای برنامه‌سازی کاربردی استاندارد KeyStore به ارائه‌دهنده AndroidKeyStore دسترسی داشته باشید.

فهرست کردن ورودی‌ها

با فراخوانی روش aliases()، ورودی‌ها را در keystore فهرست کنید:

کاتلین

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()

جاوا

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

امضا و درستی‌سنجی داده‌ها

داده‌ها را با واکشی KeyStore.Entry از keystore و استفاده از میاناهای برنامه‌سازی کاربردی Signature، مثل sign()، امضا کنید:

کاتلین

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}

جاوا

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

به‌طور مشابه، داده‌ها را با روش verify(byte[]) درستی‌سنجی کنید:

کاتلین

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}

جاوا

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

الزامی کردن اصالت‌سنجی کاربر برای استفاده از کلید

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

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

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

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

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

برنامه شما این فرایند را با فراخوانی authenticate() در نمونه‌ای از BiometricPrompt شروع می‌کند.

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

اگر کلیدی فقط از اعتبارنامه‌های زیست‌سنجشی پشتیبانی کند، هرگاه ثبت‌نام‌های زیست‌سنجشی جدیدی اضافه شود، آن کلید به‌طور پیش‌فرض نامعتبر می‌شود. می‌توانید کلید را به‌گونه‌ای پیکربندی کنید که وقتی ثبت‌نام‌های زیست‌سنجشی جدیدی اضافه می‌شود، معتبر باقی بماند. برای انجام این کار، false را به setInvalidatedByBiometricEnrollment() منتقل کنید.

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

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

قدم بعدی چیست