انبارکلید آندروید — یک مکانیسم سیستمی آندروید برای ذخیره امن کلیدهای کریپتوگرافی در انزوای سختافزاری است. این سیستم از Trusted Execution Environment (TEE) در دستگاههای دارای ARM TrustZone یا Secure Element جداگانه برای حفاظت از کلیدها در سطح تراشه استفاده میکند. به پیروی Android Open Source Project، Keystore از الگوریتمهای RSA، EC، AES و HMAC با تولید کلید مستقیماً در محیط امن پشتیبانی میکند.
نکات کلیدی
انبارکلید آندروید — یک ارائهدهنده کریپتوگرافی (provider) است که از API 1 (Android 1.0) در آندروید پیادهسازی شده، اما پشتیبانی سختافزاری کامل از Android 4.3 (API 18) فراهم شد. Keystore مسئله ذخیره امن کلیدهای خصوصی را چنان حل میکند که حتی در صورت تساهل سیستمعامل، مهاجم نتواند کلیدها را به صورت باز استخراج کند.
معماری انبارکلید آندروید از سه لایه تشکیل شده: API کاربردی (java.security.KeyStore)، خدمات سیستم (keystore daemon) و لایه سختافزاری (Keymaster HAL). برنامه از طریق API ستاندارد Java Cryptography Architecture (JCA) دسترسی پیدا میکند، و خدمات سیستم درخواستها را به Keymaster که در TEE عمل میکند هدایت میکند.
تمامی عملیاتهای کریپتوگرافیک با کلیدها (امضا، رمزگشایی) داخل TEE یا Secure Element انجام میشود. کلیدها هرگز محیط امن را ترک نمیکنند — برنامه فقط یک اشارهگر (alias) برای ارجاع به کلید دریافت میکند. این تفاوت بنیادین با KeyStoreهای نرمافزاری است که در آنها کلیدها به طور پتانسیل در حافظه فرآیند در دسترس هستند.
JKS (Java KeyStore) یا BKS (Bouncy Castle) ستاندارد کلیدها را در پروندههایی که با رمز عبور محافظت میشوند ذخیره میکنند. Android Keystore کلیدها را در انزوای سختافزاری ذخیره میکند که حتی از کاربر root نیز محافظت میشوند. JKS در برابر دسترسی مستقیم به فایل سیستم آسیبپذیر است، Android Keystore — نیست.
تفاوت دیگر ایناست که در Android Keystore کلیدها پارامترهای استفاده سختگیرانهای (purpose — فقط sign/verify/encrypt/decrypt) دارند که در زمان تولید تعیین میشوند. این پارامترها پس از آن قابل تغییر نیستند که از سوءاستفاده از کلید جلوگیری میکند.
در زمان ایجاد یک کلید جدید، برنامه KeyPairGenerator یا KeyGenerator را با KeyGenParameterSpec که شامل تمامی پارامترهای کلید آینده است فراخوان میکند. سیستم درخواست را به Keymaster HAL ارسال میکند که کلید را داخل TEE تولید و یک اشارهگر برمیگرداند.
روش KeyGenParameterSpec.Builder پارامترهای اجباری را میپذیرد: نام کلید در Keystore، کاربرد (PURPOSE_SIGN، PURPOSE_ENCRYPT)، الگوریتم (RSA، EC، AES). به علاوه: digest (SHA-256)، padding (PKCS7)، userAuthenticationRequired (بیومتری)، keyValidityStart/End (محدودیتهای زمانی).
پس از تعیین پارامترها، KeyPairGenerator.generateKeyPair() یک KeyPair برمیگرداند که PrivateKey یک شیئ است که عملیات را به Keymaster ارجاع میدهد. کلید عمومی قابل استخراج است، کلید خصوصی — خیر. آن فقط داخل TEE وجود دارد.
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
fun generateKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
.build()
val kpGen = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpGen.initialize(spec)
kpGen.generateKeyPair()
}
Signature برای ECDSA یا RSA-PSS از طریق API ستاندارد ایجاد میشود: Signature.getInstance(algorithm).initSign(privateKey). عملیات امضا در TEE انجام میشود: برنامه دادهها را ارسال میکند، Keymaster آنها را به صورت سختافزاری امضا میکند و امضا را برمیگرداند. کلید و دادهها در حافظه مشترک مخلوط نمیشوند.
برای حفاظت بیومتریک، قبل از امضا باید کاربر از طریق BiometricPrompt احراز هویت شود. بدون احراز هویت موفقیتآمیز، Keymaster عملیات را انجام نمیدهد و CryptoAuthenticationException برمیگرداند.
import java.security.KeyStore
import java.security.Signature
import androidx.biometric.BiometricPrompt
fun signWithBiometric(alias: String) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val entry = ks.getEntry(alias, null) as KeyStore.PrivateKeyEntry
val signature = Signature.getInstance("SHA256withRSA")
signature.initSign(entry.privateKey)
// BiometricPrompt با CryptoObject(signature) FaceID/PIN را درخواست میکند
}
آندروید از دو حالت ذخیره کلید پشتیبانی میکند: نرمافزاری (در دستگاههای بدون TEE) و سختافزاری (در دستگاههای دارای TEE یا Secure Element). حالت به قابلیتهای SoC و نسخه آندروید بستگی دارد.
در دستگاههای بدون Trusted Execution Environment (قبل از Android 4.3 یا SoCهای ارزان) کلیدها با استفاده از یک کلید اصلی که از رمز صفحه قفل بهدست آمده در شکل رمزگذاریشده ذخیره میشوند. این حالت امنیت کمتری دارد — کلیدها در زمان انجام عملیات کریپتوگرافی در حافظه فرآیند در دسترس هستند.
سطح حفاظت بر اساس رمزگذاری فایل KeyStore با AES-256-GCM است. کلید رمزگذاری بر اساس رمز عبور یا PIN کاربر از طریق Scrypt (PBKDF2 با تعداد ایتراسیون بالا) تولید میشود.
در دستگاههای مدرن Keymaster 4.x در TEE (ARM TrustZone) استفاده میشود. کلیدها فقط داخل TrustZone تولید، ذخیره و استفاده میشوند. حتی هسته لینوکس به کلیدهای خصوصی دسترسی ندارد — فقط Keymaster HAL میتواند عملیات را انجام دهد.
Secure Element (مانند eSE در Samsung Knox یا StrongBox در Google Pixel 3+) — یک تراشه جداگانه با پردازنده و حافظه خود است. آن گواهی Common Criteria EAL 4+ را دارد و بالاترین سطح حفاظت را از جمله محافظت در برابر بازکردن فیزیکی فراهم میکند.
| نوع | مکان ذخیره | سطح حفاظت | از API |
|---|---|---|---|
| Software | فایل /data/misc/keystore | متوسط (AES-256) | API 1+ |
| Keymaster 3 | TEE (TrustZone) | بالا | API 23+ |
| Keymaster 4 | TEE + Secure I/O | خیلی بالا | API 28+ |
| StrongBox | Secure Element سختافزاری | حداکثر | API 28+، اختیاری |
Android Keystore با Java Cryptography Architecture (JCA) ادغام شده است. برای دسترسی به ارائهدهنده، KeyStore.getInstance("AndroidKeyStore") استاندارد استفاده میشود. API از API 18 موجود است.
روش KeyStore.load(null) کانتینر KeyStore برنامه جاری را بارگذاری میکند. رمز عبور مورد نیاز نیست — Android از کنتکست برنامه و UID آن برای جداسازی دسترسی استفاده میکند. هر برنامه فقط ورودیهای خود را میبیند، مگر اینکه از UID مشترک استفاده شود.
روشهای setEntry و getEntry با KeyStore.PrivateKeyEntry، SecretKeyEntry یا TrustedCertificateEntry کار میکنند. پارامتر ProtectionParameter همیشه برای Android Keystore null است (حفاظت در سطح سیستم پیاده شده است).
import java.security.KeyStore
import java.security.cert.Certificate
import android.security.keystore.KeyProtection
fun storeSecretKey(alias: String, key: SecretKey) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val prot = KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build()
ks.setEntry(alias, KeyStore.SecretKeyEntry(key), prot)
}
با استفاده از KeyCharacteristics میتوان تعیین کرد که کلید در چه محیطی ذخیره شده: KeyStore نرمافزاری، TEE یا StrongBox. روش getKeyCharacteristics() یک مجموعه علمت برمیگرداند: FLAG_HARDWARE (keymaster)، FLAG_SECURE_ELEMENT (StrongBox)، FLAG_TRUSTED_USER_PRESENCE_REQUIRED (بیومتری).
Android Keystore از یک مجموعه گسترده از الگوریتمهای کریپتوگرافیک پشتیبانی میکند که به سه دسته تقسیم میشود: نامتماثل، متماثل و MAC. پشتیبانی از الگوریتمهای مشخص به نسخه Keymaster HAL بستگی دارد.
RSA (1024–4096 بیت) — برای امضا (PKCS1، PSS) و رمزگذاری (OAEP، PKCS1). EC (P-224، P-256، P-384، P-521) — برای امضای ECDSA و توافق ECDH. AES (128، 256 بیت) — برای رمزگذاری متماثل در حالتهای CBC، CTR، GCM. HMAC (SHA1، SHA256، SHA512) — برای احراز هویت پیام.
برای هر کلید، setPurposes تعیین شده است که عملیات ممکن را محدود میکند. یک کلید RSA با PURPOSE_SIGN نمیتواند برای رمزگذاری استفاده شود، حتی اگر مهاجم به API دسترسی داشته باشد. این اجبار استفاده از کلید در سطح سختافزار است.
Keymaster شمارنده تلاشهای ناموفق احراز هویت بیومتریک را دارد. پس از تعداد مشخصی تلاش ناموفق (از طریق setInvalidatedByBiometricEnrollment قابل تنظیم) کلید غیرقابل دسترس شده و نیازمند حذف/تولید مجدد است. پس از حذف تمامی الگوهای بیومتریک، تمامی کلیدهای با userAuthenticationRequired=true به طور خودکار بیاعتبار میشوند.
همچنین Key Attestation (Android 8.1+) پشتیبانی میشود: به درخواست برنامه، Keymaster یک گواهینامه با اطلاعاتی درباره ویژگیهای کلید (سختافزاری/نرمافزاری، الگوریتم، purges) امضا میکند. سرور میتواند این گواهینامه را برای تأیید اینکه کلید در محیط امن ایجاد شده است بررسی کند.
سوالات متداول
Java KeyStore کلیدها را در یک فایل محافظتشده با رمز عبور (JKS، BKS) ذخیره میکند. Android Keystore از انزوای سختافزاری TEE یا Secure Element استفاده میکند. Java KeyStore در برابر دسترسی root آسیبپذیر است، Android Keystore — نیست، زیرا کلیدهای خصوصی هرگز محیط امن را ترک نمیکنند.
بله، از طریق KeyStore.setEntry با KeyProtection. اما کلید واردشده حفاظت سختافزاری ندارد — در KeyStore نرمافزاری که با کلید اصلی رمزگذاری شده ذخیره میشود. برای حداکثر امنیت، همیشه کلیدها را داخل Keystore تولید کنید.
از KeyChain.isBoundKeyAlgorithm استفاده کنید یا KeyCharacteristics را پس از تولید کلید بررسی کنید. وجود FLAG_HARDWARE در ویژگیها به معنای این است که کلید در TEE ایجاد شده است. همچنین میتوانید android.security.keystore.isHardwareBacked() را بررسی کنید.
پس از حذف برنامه، Android تمامی کلیدهای آن را از Keystore حذف میکند. دادهها به طور جبرانناپذیر از دست میروند. در نصب مجدد، برنامه باید کلیدهای جدیدی تولید کند. پیشتیان از کلیدها از طریق TEE به دلایل معماری ممکن نیست.
در دستگاه قفلشده، Keymaster هیچ عملیاتی انجام نمیدهد. کلیدهای با userAuthenticationRequired=true هر بار نیازمند تأیید بیومتریک هستند. حتی با دسترسی root، مهاجم نمیتواند مستقیماً با Keymaster تماس بگیرد — فقط از طریق خدمات Android Keystore.
نتیجه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید