Sistem Android Keystore

Sistem Android Keystore memungkinkan Anda menyimpan kunci kriptografis dalam penampung agar lebih sulit untuk diekstrak dari perangkat. Setelah kunci berada di keystore, Anda dapat menggunakannya untuk operasi kriptografi, dan materi kunci tersebut tetap tidak dapat diekspor. Selain itu, sistem keystore memungkinkan Anda membatasi waktu dan cara penggunaan kunci, misalnya mewajibkan autentikasi pengguna untuk menggunakan kunci, atau membatasi kunci agar hanya digunakan dalam mode kriptografi tertentu. Lihat bagian Fitur Keamanan untuk mengetahui informasi selengkapnya.

Sistem keystore digunakan oleh KeyChain API, diperkenalkan di Android 4.0 (level API 14), serta fitur penyedia Android Keystore, diperkenalkan di Android 4.3 (level API 18). Dokumen ini menjelaskan waktu dan cara Anda menggunakan sistem Android Keystore.

Fitur keamanan

Sistem Android Keystore melindungi materi kunci dari penggunaan tanpa izin dengan dua cara. Pertama, mengurangi risiko penggunaan materi kunci tanpa izin dari luar perangkat Android dengan mencegah ekstraksi materi kunci dari proses aplikasi dan dari perangkat Android secara keseluruhan. Kedua, sistem keystore mengurangi risiko penggunaan materi kunci tanpa izin dalam perangkat Android dengan membuat aplikasi menentukan penggunaan kuncinya dengan izin, lalu menerapkan batasan tersebut di luar proses aplikasi.

Pencegahan ekstraksi

Materi kunci dari kunci Android Keystore dilindungi dari ekstraksi menggunakan dua langkah pengamanan:

  • Materi kunci tidak memasuki proses aplikasi. Saat sebuah aplikasi menjalankan operasi kriptografi menggunakan kunci Android Keystore, di balik layar, teks biasa, ciphertext, dan pesan yang perlu ditandatangani atau diverifikasi diteruskan ke proses sistem yang menjalankan operasi kriptografi tersebut. Jika proses aplikasi disusupi, penyerang mungkin dapat menggunakan kunci aplikasi, tetapi tidak dapat mengekstrak materi kuncinya (misalnya, untuk digunakan di luar perangkat Android).
  • Materi kunci dapat terikat dengan hardware aman perangkat Android, seperti Trusted Execution Environment (TEE) atau Elemen Pengaman (SE). Jika fitur ini diaktifkan untuk sebuah kunci, materi kuncinya tidak akan diekspos di luar hardware yang aman. Jika Android OS disusupi atau penyerang dapat membaca penyimpanan internal perangkat, penyerang mungkin dapat menggunakan kunci Keystore Android aplikasi di perangkat Android, tetapi tidak dapat mengekstraknya dari perangkat. Fitur ini hanya akan aktif jika hardware aman perangkat mendukung kombinasi tertentu algoritma kunci, mode pemblokiran, skema padding, dan digest yang diizinkan untuk digunakan dengan kunci.

    Untuk memeriksa apakah fitur ini diaktifkan untuk sebuah kunci, dapatkan KeyInfo untuk kunci tersebut. Langkah berikutnya bergantung pada versi SDK target aplikasi Anda:

    • Jika aplikasi Anda menargetkan Android 10 (level API 29) atau yang lebih tinggi, periksa nilai return getSecurityLevel(). Nilai return yang cocok dengan KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT atau KeyProperties.SecurityLevelEnum.STRONGBOX menunjukkan bahwa kunci tersebut berada dalam hardware yang aman.
    • Jika aplikasi Anda menargetkan Android 9 (level API 28) atau yang lebih rendah, periksa nilai return boolean KeyInfo.isInsideSecurityHardware().

Modul keamanan hardware

Perangkat yang didukung dan menjalankan Android 9 (level API 28) atau yang lebih tinggi dapat memiliki StrongBox Keymaster, implementasi Keymaster atau Keymint HAL yang berada di elemen pengaman yang mirip dengan modul keamanan hardware. Meskipun modul keamanan hardware dapat merujuk ke berbagai implementasi penyimpanan kunci yang tidak dapat terungkap oleh penyusupan kernel Linux, seperti TEE, StrongBox secara eksplisit merujuk pada perangkat seperti Elemen Pengaman tersemat (eSE) atau unit pemrosesan yang aman (iSE) di SoC.

Modul ini berisi hal-hal berikut:

  • CPU-nya sendiri
  • Penyimpanan aman
  • Generator angka-acak yang sesungguhnya.
  • Mekanisme tambahan untuk mencegah modifikasi paket dan sideload aplikasi tanpa izin
  • Timer yang aman
  • Pin notifikasi reboot (atau yang setara), seperti input/output tujuan umum (GPIO)

Untuk mendukung implementasi StrongBox rendah daya, subset algoritma dan ukuran kunci berikut didukung:

  • RSA 2048
  • AES 128 dan 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (mendukung ukuran kunci antara 8 dan 64 byte, inklusif)
  • Triple DES
  • APDU yang Diperpanjang
  • Pengesahan Kunci
  • Dukungan Amendemen H untuk upgrade

Saat membuat atau mengimpor kunci menggunakan class KeyStore, indikasikan preferensi untuk menyimpan kunci di StrongBox Keymaster dengan meneruskan true ke metode setIsStrongBoxBacked().

Meskipun StrongBox sedikit lebih lambat dan memiliki keterbatasan resource (artinya mendukung lebih sedikit operasi serentak) dibandingkan TEE, StrongBox memberikan jaminan keamanan yang lebih baik terhadap serangan fisik dan side-channel. Jika Anda ingin memprioritaskan jaminan keamanan yang lebih tinggi daripada efisiensi resource aplikasi, sebaiknya gunakan StrongBox di perangkat tempatnya tersedia. Di mana pun StrongBox tidak tersedia, aplikasi Anda selalu dapat kembali ke TEE untuk menyimpan materi kunci.

Otorisasi penggunaan kunci

Untuk menghindari penggunaan kunci tanpa izin di perangkat Android, Android Keystore mengizinkan aplikasi untuk menentukan penggunaan kuncinya dengan izin saat membuat atau mengimpor kunci. Setelah kunci dibuat atau diimpor, otorisasinya tidak dapat diubah. Selanjutnya, otorisasi diberlakukan oleh Android Keystore setiap kali kunci digunakan. Otorisasi ini merupakan fitur keamanan lanjutan yang umumnya hanya berguna jika Anda menetapkan bahwa peretasan proses aplikasi setelah kunci dibuat/diimpor (tetapi tidak sebelum atau selama dibuat/diimpor) tidak akan mengakibatkan penggunaan kunci tersebut tanpa izin.

Otorisasi penggunaan kunci yang didukung termasuk ke dalam kategori berikut:

  • Kriptografi: kunci hanya dapat digunakan dengan algoritma, operasi, atau tujuan kunci yang diotorisasi (mengenkripsi, mendekripsi, menandatangani, memverifikasi), skema padding, mode pemblokiran, atau digest.
  • Interval validitas sementara: kunci diotorisasi hanya untuk digunakan selama interval waktu yang ditentukan.
  • Autentikasi pengguna: kunci hanya dapat digunakan jika pengguna telah diautentikasi baru-baru ini. Lihat Mewajibkan autentikasi pengguna untuk penggunaan kunci.

Sebagai langkah keamanan tambahan untuk kunci yang materi kuncinya berada dalam hardware aman (lihat KeyInfo.isInsideSecurityHardware() atau, untuk aplikasi yang menargetkan Android 10 (level API 29) atau yang lebih tinggi, KeyInfo.getSecurityLevel()), beberapa otorisasi penggunaan kunci mungkin diberlakukan oleh hardware aman, bergantung pada perangkat Android. Hardware aman biasanya menerapkan otorisasi kriptografi dan autentikasi pengguna. Namun, hardware aman biasanya tidak menerapkan otorisasi interval validitas sementara, karena hardware ini biasanya tidak memiliki jam real-time yang independen dan aman.

Anda dapat meminta kueri apakah otorisasi autentikasi pengguna kunci diberlakukan oleh hardware yang aman menggunakan KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Memilih antara keychain dan penyedia Android Keystore

Gunakan KeyChain API jika Anda menginginkan kredensial berskala sistem. Ketika suatu aplikasi meminta penggunaan kredensial apa pun melalui KeyChain API, pengguna dapat memilih kredensial terinstal mana yang dapat diakses oleh suatu aplikasi, melalui UI yang disediakan oleh sistem. Dengan cara ini, beberapa aplikasi dapat menggunakan kumpulan kredensial yang sama dengan izin pengguna.

Gunakan penyedia Android Keystore untuk mengizinkan setiap aplikasi menyimpan kredensialnya sendiri, yang hanya dapat diakses oleh aplikasi tersebut. Cara ini memungkinkan aplikasi mengelola kredensial yang hanya dapat digunakan oleh aplikasi itu sendiri, sekaligus memberikan manfaat keamanan yang sama seperti yang diberikan KeyChain API untuk kredensial seluruh sistem. Metode ini tidak mengharuskan pengguna memilih kredensial.

Menggunakan penyedia Android Keystore

Untuk menggunakan fitur ini, gunakan class standar KeyStore dan KeyPairGenerator atau KeyGenerator bersama dengan penyedia AndroidKeyStore yang diperkenalkan di Android 4.3 (level API 18).

AndroidKeyStore didaftarkan sebagai jenis KeyStore untuk digunakan dengan metode KeyStore.getInstance(type), dan sebagai penyedia untuk digunakan dengan metode KeyPairGenerator.getInstance(algorithm, provider) dan KeyGenerator.getInstance(algorithm, provider).

Membuat kunci pribadi atau kunci rahasia baru

Untuk membuat KeyPair baru yang berisi PrivateKey, Anda harus menentukan atribut X.509 awal sertifikat. Anda dapat menggunakan KeyStore.setKeyEntry() untuk mengganti sertifikat di kemudian hari dengan sertifikat yang ditandatangani oleh certificate authority (CA).

Untuk membuat pasangan kunci, gunakan KeyPairGenerator dengan KeyGenParameterSpec.

Kotlin

/*
 * 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()

Java

/*
 * 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();

Mengimpor kunci terenkripsi ke hardware yang aman

Android 9 (level API 28) dan yang lebih tinggi memungkinkan Anda mengimpor kunci terenkripsi dengan aman ke keystore menggunakan format kunci yang dienkode dengan ASN.1. Keymaster kemudian akan mendekripsi kunci dalam keystore, sehingga isi kunci tidak pernah muncul sebagai teks biasa dalam memori host perangkat. Proses ini memberikan keamanan dekripsi kunci tambahan.

Untuk mendukung pengimporan kunci terenkripsi secara aman ke keystore, lakukan langkah-langkah berikut:

  1. Buat pasangan kunci yang menggunakan fungsi PURPOSE_WRAP_KEY. Sebaiknya Anda juga menambahkan pengesahan ke pasangan kunci ini.

  2. Pada server atau komputer yang Anda percayai, buat pesan ASN.1 untuk SecureKeyWrapper.

    Wrapper akan berisi skema berikut:

       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. Buat objek WrappedKeyEntry, lalu teruskan pesan ASN.1 sebagai array byte.

  4. Teruskan objek WrappedKeyEntry ini ke overload setEntry() yang menerima objek Keystore.Entry.

Menggunakan entri keystore

Anda dapat mengakses penyedia AndroidKeyStore melalui semua KeyStore API standar.

Mencantumkan entri

Cantumkan entri di keystore dengan memanggil metode aliases():

Kotlin

/*
 * 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()

Java

/*
 * 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();

Menandatangani dan memverifikasi data

Tanda tangani data dengan mengambil KeyStore.Entry dari keystore dan menggunakan Signature API, seperti sign():

Kotlin

/*
 * 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()
}

Java

/*
 * 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();

Demikian pula, verifikasikan data dengan metode verify(byte[]):

Kotlin

/*
 * 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)
}

Java

/*
 * 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);

Mewajibkan autentikasi pengguna untuk penggunaan kunci

Saat membuat atau mengimpor kunci ke AndroidKeyStore, Anda dapat menetapkan agar kunci hanya boleh digunakan jika pengguna telah diautentikasi. Pengguna diautentikasi menggunakan salah satu kredensial layar kunci aman mereka (pola/PIN/sandi, kredensial biometrik).

Autentikasi ini merupakan fitur keamanan lanjutan yang umumnya hanya berguna jika Anda menetapkan bahwa peretasan proses aplikasi setelah kunci dibuat/diimpor (tetapi tidak sebelum atau selama dibuat/diimpor) tidak dapat mengabaikan keharusan pengguna untuk diautentikasi agar dapat menggunakan kunci.

Ketika kunci hanya diizinkan untuk digunakan jika pengguna telah diautentikasi, Anda dapat memanggil setUserAuthenticationParameters() untuk mengonfigurasinya agar beroperasi dalam salah satu mode berikut:

Memberi otorisasi selama durasi waktu tertentu
Semua kunci diizinkan untuk digunakan segera setelah pengguna melakukan autentikasi menggunakan salah satu kredensial yang ditentukan.
Memberi otorisasi selama durasi operasi kriptografi tertentu

Setiap operasi yang melibatkan kunci tertentu harus diizinkan satu per satu oleh pengguna.

Aplikasi Anda memulai proses ini dengan memanggil authenticate() di instance BiometricPrompt.

Untuk setiap kunci yang dibuat, Anda dapat memilih untuk mendukung kredensial biometrik yang kuat, kredensial layar kunci, atau kedua jenis kredensial. Untuk menentukan apakah pengguna telah menyiapkan kredensial yang diandalkan oleh kunci aplikasi Anda, panggil canAuthenticate().

Jika kunci hanya mendukung kredensial biometrik, kunci tersebut akan dibatalkan secara default setiap kali pendaftaran biometrik baru ditambahkan. Anda dapat mengonfigurasi kunci agar tetap valid saat pendaftaran biometrik baru ditambahkan. Untuk melakukannya, teruskan false ke setInvalidatedByBiometricEnrollment().

Pelajari lebih lanjut cara menambahkan kemampuan autentikasi biometrik ke aplikasi Anda, termasuk cara menampilkan dialog autentikasi biometrik.

Algoritma yang didukung

Artikel blog

Lihat entri blog Unifying Key Store Access in ICS.