Система хранения ключей Android

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

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

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

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

Предотвращение экстракции

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

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

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

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

Аппаратный модуль безопасности

Поддерживаемые устройства под управлением Android 9 (уровень API 28) или выше могут иметь StrongBox Keymaster — реализацию Keymaster или Keymint HAL, которая находится в защищенном элементе, подобном аппаратному модулю безопасности. В то время как модули аппаратной безопасности могут относиться ко многим различным реализациям хранилища ключей, где компрометация ядра Linux не может их раскрыть, например TEE, StrongBox явно относится к таким устройствам, как встроенные элементы безопасности (eSE) или защищенные процессоры на SoC ( ИСЕ).

Модуль содержит следующее:

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

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

  • ЮАР 2048
  • АЕС 128 и 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (поддерживает размеры ключей от 8 до 64 байт включительно)
  • Тройной DES
  • APDU увеличенной длины
  • Ключевая аттестация
  • Поддержка поправки H для обновления

При создании или импорте ключей с использованием класса KeyStore вы указываете предпочтение сохранения ключа в мастере ключей StrongBox, передавая true методу setIsStrongBoxBacked() .

Хотя StrongBox немного медленнее и ограничен в ресурсах (это означает, что он поддерживает меньшее количество одновременных операций) по сравнению с TEE, StrongBox обеспечивает лучшие гарантии безопасности от физических атак и атак по побочным каналам. Если вы хотите отдать предпочтение более высоким гарантиям безопасности, а не эффективности ресурсов приложения, мы рекомендуем использовать StrongBox на тех устройствах, где он доступен. Там, где StrongBox недоступен, ваше приложение всегда может вернуться к TEE для хранения ключевых материалов.

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

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

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

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

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

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

Выбирайте между связкой ключей и поставщиком хранилища ключей Android.

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

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

Используйте поставщика хранилища ключей Android

Чтобы использовать эту функцию, вы используете стандартные классы 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() чтобы позже заменить сертификат сертификатом, подписанным центром сертификации (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 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() :

Котлин

/*
 * 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 из хранилища ключей и используя API-интерфейсы 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() .

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

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

Статьи в блоге

См. запись в блоге «Унификация доступа к хранилищу ключей в ICS» .