Система Android Keystore

Система Android Keystore позволяет хранить криптографические ключи в контейнере, чтобы их было сложнее извлечь из устройства. После этого вы сможете использовать их для криптографических операций, но не сможете экспортировать. Кроме того, система хранилища ключей позволяет ограничивать время и способ использования ключей, например требовать аутентификацию пользователя для использования ключа или ограничивать использование ключей только в определенных криптографических режимах. Подробную информацию вы найдете в разделе Функции безопасности.

Система Keystore используется API KeyChain, который появился в Android 4.0 (уровень API 14), а также поставщиком Android Keystore, который появился в Android 4.3 (уровень API 18). В этом документе рассказывается, когда и как использовать систему Android Keystore.

Функции безопасности

Система Android Keystore защищает ключи от несанкционированного использования двумя способами. Во-первых, снижается риск несанкционированного использования материала ключей вне устройства Android, поскольку материал ключей нельзя извлечь из процессов приложения и устройства Android в целом. Во-вторых, система хранилища ключей снижает риск несанкционированного использования ключей внутри устройства Android. Для этого приложения должны указывать разрешенные способы использования своих ключей, а затем применять эти ограничения вне процессов приложений.

Предотвращение извлечения

Материал ключей Android Keystore защищен от извлечения двумя мерами безопасности:

  • Материал ключа никогда не попадает в процесс приложения. Когда приложение выполняет криптографические операции с помощью ключа Android Keystore, зашифрованный и незашифрованный текст, а также сообщения, которые нужно подписать или проверить, передаются системному процессу, выполняющему криптографические операции. Если процесс приложения будет скомпрометирован, злоумышленник сможет использовать ключи приложения, но не сможет извлечь их материал (например, чтобы использовать за пределами устройства Android).
  • Материал ключа можно привязать к защищенному оборудованию устройства Android, например к доверенной среде выполнения (TEE) или защитному элементу (SE). Если эта функция включена для ключа, его материал никогда не будет доступен за пределами защищенного оборудования. Если ОС Android взломана или злоумышленник может читать данные из внутреннего хранилища устройства, он может использовать ключи Android Keystore любого приложения на устройстве Android, но не может извлечь их из устройства. Эта функция включена, только если защищенное аппаратное обеспечение устройства поддерживает определенную комбинацию алгоритма ключа, режимов блокировки, схем заполнения и дайджестов, с которыми разрешено использовать ключ.

    Чтобы проверить, включена ли функция для ключа, получите для него KeyInfo. Дальнейшие действия зависят от целевой версии SDK вашего приложения.

    • Если ваше приложение предназначено для Android 10 (уровень API 29) или более поздней версии, проверьте возвращаемое значение getSecurityLevel(). Возвращаемые значения, соответствующие KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT или KeyProperties.SecurityLevelEnum.STRONGBOX, указывают на то, что ключ находится в защищенном аппаратном обеспечении.
    • Если ваше приложение предназначено для Android 9 (уровень API 28) или более ранней версии, проверьте логическое возвращаемое значение KeyInfo.isInsideSecurityHardware().

Защитный элемент StrongBox KeyMint

Устройства с Android 9 (уровень API 28) или более поздней версии могут включать StrongBox KeyMint – реализацию KeyMint HAL, поддерживаемую StrongBox. Аппаратные модули безопасности (HSM) – это решения для безопасного хранения ключей, устойчивые к компрометации ядра Linux. StrongBox – это реализация в виде встроенных элементов безопасности или интегрированных анклавов безопасности (iSE), обеспечивающая более надежную изоляцию и защиту от несанкционированного доступа по сравнению с TEE.

Реализация StrongBox KeyMint должна содержать следующее:

  • Собственный процессор
  • Безопасное хранение данных
  • Генератор истинно случайных чисел
  • Дополнительные механизмы защиты от вмешательства в пакеты и несанкционированной установки приложений из неизвестных источников.
  • Защищенный таймер
  • Контакт уведомления о перезагрузке (или эквивалент), например вход/выход общего назначения (GPIO).

Для поддержки маломощных реализаций StrongBox поддерживается подмножество алгоритмов и размеров ключей:

  • RSA 2048
  • AES 128 и 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (поддерживает ключи размером от 8 до 64 байт включительно);
  • Triple DES
  • Блоки APDU увеличенной длины

StrongBox также поддерживает аттестацию ключей.

Использовать KeyMint в StrongBox

Чтобы проверить, доступно ли хранилище StrongBox на устройстве, используйте FEATURE_STRONGBOX_KEYSTORE. Если StrongBox доступен, вы можете указать, что ключ нужно хранить в StrongBox KeyMint, передав true в следующие методы:

Если StrongBox KeyMint не поддерживает указанный алгоритм или размер ключа, фреймворк выдаст ошибку StrongBoxUnavailableException. В этом случае сгенерируйте или импортируйте ключ, не вызывая setIsStrongBoxBacked(true).

Разрешения на использование ключей

Чтобы предотвратить несанкционированное использование ключей на устройстве Android, Android Keystore позволяет приложениям указывать разрешенные способы использования ключей при их создании или импорте. После создания или импорта ключа его авторизации нельзя изменить. Затем разрешения применяются хранилищем ключей Android при каждом использовании ключа. Это расширенная функция безопасности, которая обычно полезна только в том случае, если ваши требования заключаются в том, что компрометация процесса вашего приложения после генерации/импорта ключа (но не до или во время) не может привести к несанкционированному использованию ключа.

Поддерживаемые разрешения на использование ключей делятся на следующие категории:

  • Криптография. Ключ можно использовать только с авторизованными алгоритмами, операциями или целями (шифрование, расшифровка, подпись, проверка), схемами заполнения, блочными режимами или дайджестами.
  • Временной интервал действия. Ключ можно использовать только в течение определенного периода времени.
  • Аутентификация пользователя. Ключ можно использовать, только если пользователь недавно прошел аутентификацию. Подробнее о том, как настроить обязательную аутентификацию пользователей для использования ключа…

В качестве дополнительной меры безопасности для ключей, материал которых находится в защищенном аппаратном обеспечении (см. KeyInfo.isInsideSecurityHardware() или, для приложений, предназначенных для Android 10 (API уровня 29) или более поздней версии, KeyInfo.getSecurityLevel()), некоторые разрешения на использование ключей могут применяться защищенным аппаратным обеспечением в зависимости от устройства Android. Защищенное аппаратное обеспечение обычно обеспечивает криптографическую авторизацию и авторизацию пользователей. Однако защищенное аппаратное обеспечение обычно не применяет авторизацию по временному интервалу действия, поскольку у него нет независимых защищенных часов реального времени.

Вы можете запросить, применяется ли авторизация аутентификации пользователя ключа защищенным аппаратным обеспечением, используя KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Выбор между Keychain и Android Keystore

Используйте KeyChain API, если вам нужны учетные данные на уровне системы. Когда приложение запрашивает использование учетных данных через API KeyChain, пользователи могут выбрать в системном интерфейсе, к каким из установленных учетных данных приложение получит доступ. Это позволяет нескольким приложениям использовать один и тот же набор учетных данных с согласия пользователя.

Используйте поставщика Android Keystore, чтобы приложение могло хранить собственные учетные данные, доступные только ему. Это позволяет приложениям управлять учетными данными, которые могут использовать только они, и обеспечивает те же преимущества в плане безопасности, что и API KeyChain для учетных данных на уровне системы. При этом пользователю не нужно выбирать учетные данные.

Используйте поставщика Android Keystore

Чтобы использовать эту функцию, используйте стандартные классы KeyStore и KeyPairGenerator или KeyGenerator вместе с поставщиком AndroidKeyStore, представленным в Android 4.3 (уровень API 18).

AndroidKeyStore регистрируется как тип KeyStore для использования с методом KeyStore.getInstance(type) и как поставщик для использования с методами KeyPairGenerator.getInstance(algorithm, provider) и KeyGenerator.getInstance(algorithm, provider).

Поскольку криптографические операции могут занимать много времени, приложениям не следует использовать AndroidKeyStore в основном потоке, чтобы интерфейс оставался отзывчивым. (StrictMode может помочь вам найти места, где это не так.)

Создайте новый закрытый или секретный ключ

Чтобы создать новый KeyPair, содержащий PrivateKey, необходимо указать начальные атрибуты X.509 сертификата. Вы можете использовать KeyStore.setKeyEntry(), чтобы позже заменить сертификат на подписанный центром сертификации.

Чтобы создать пару ключей, используйте KeyPairGenerator с 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();

Импорт зашифрованных ключей в защищенное аппаратное обеспечение

В Android 9 (уровень API 28) и более поздних версиях можно безопасно импортировать зашифрованные ключи в хранилище ключей, используя формат ключа с кодировкой ASN.1. Затем Keymaster расшифровывает ключи в хранилище, поэтому их содержимое никогда не отображается в виде обычного текста в памяти хоста устройства. Этот процесс обеспечивает дополнительную защиту при расшифровке ключа.

Чтобы обеспечить безопасный импорт зашифрованных ключей в хранилище, выполните следующие действия:

  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.

Как работать с записями хранилища ключей

Доступ к поставщику AndroidKeyStore можно получить через все стандартные API KeyStore.

Получение списка записей

Вызовите метод 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();

Подписывать и проверять данные

Подпишите данные, получив KeyStore.Entry из хранилища ключей и используя API Signature, например 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();

Аналогичным образом проверьте данные с помощью метода 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);

Требовать аутентификацию пользователя для использования ключа

При создании или импорте ключа в AndroidKeyStore вы можете указать, что ключ разрешено использовать только в том случае, если пользователь прошел аутентификацию. Для аутентификации пользователя используется часть его учетных данных для разблокировки экрана (графический ключ, PIN-код, пароль или биометрические данные).

Это расширенная функция безопасности, которая обычно полезна, только если ваши требования таковы, что компрометация процесса приложения после создания/импорта ключа (но не до или во время) не может обойти требование аутентификации пользователя для использования ключа.

Если ключ разрешено использовать только после аутентификации пользователя, вы можете вызвать метод setUserAuthenticationParameters(), чтобы настроить работу ключа в одном из следующих режимов:

Как предоставить доступ на определенный период времени
Все ключи авторизуются для использования, как только пользователь выполняет аутентификацию с помощью одних из указанных учетных данных.
Авторизация на время выполнения определенной криптографической операции

Каждая операция с определенным ключом должна быть авторизована пользователем.

Приложение начинает этот процесс, вызывая authenticate() в экземпляре BiometricPrompt.

Для каждого создаваемого ключа можно выбрать поддержку надежных биометрических учетных данных, учетных данных для разблокировки экрана или обоих типов учетных данных. Чтобы определить, настроил ли пользователь учетные данные, на которые опирается ключ вашего приложения, вызовите canAuthenticate().

Если ключ поддерживает только биометрические учетные данные, он по умолчанию становится недействительным при добавлении новых биометрических данных. Вы можете настроить ключ так, чтобы он оставался действительным при добавлении новых биометрических данных. Для этого передайте false в setInvalidatedByBiometricEnrollment().

Узнайте, как добавить в приложение биометрическую аутентификацию, в том числе диалоговое окно для нее.

Поддерживаемые алгоритмы

Что дальше

  • Прочитайте статью Unifying Key Store Access in ICS (Объединение доступа к хранилищу ключей в ICS) в блоге для разработчиков Android.