Система 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 10 (уровень API 29) или выше, проверьте возвращаемое значение
Защищенный элемент StrongBox KeyMint
Устройства под управлением Android 9 (уровень API 28) или выше могут включать StrongBox KeyMint , реализацию KeyMint HAL, поддерживаемую StrongBox . В то время как модули аппаратной безопасности (HSM) в целом относятся к безопасным решениям для хранения ключей, устойчивым к компрометации ядра Linux, StrongBox конкретно обозначает реализации во встроенных SE или интегрированных защищенных анклавах (iSE), обеспечивающих более сильную изоляцию и устойчивость к несанкционированному вмешательству по сравнению с TEE.
Реализация StrongBox KeyMint должна содержать следующее:
- Свой процессор
- Безопасное хранение
- Настоящий генератор случайных чисел
- Дополнительные механизмы для защиты от подделки пакетов и несанкционированной загрузки приложений.
- Безопасный таймер
- Контакт уведомления о перезагрузке (или его эквивалент), например ввод/вывод общего назначения (GPIO).
Поддерживается подмножество алгоритмов и размеров ключей для реализации маломощных реализаций StrongBox:
- ЮАР 2048
- АЕС 128 и 256
- ECDSA, ECDH P-256
- HMAC-SHA256 (поддерживает размеры ключей от 8 до 64 байт включительно)
- Тройной DES
- APDU увеличенной длины
StrongBox также поддерживает аттестацию ключей .
Используйте StrongBox KeyMint
Используйте FEATURE_STRONGBOX_KEYSTORE
, чтобы проверить, доступен ли StrongBox на устройстве. Если StrongBox доступен, вы можете указать предпочтение хранения ключа в StrongBox KeyMint, передав true
следующим методам:
- Генерация ключей:
KeyGenParameterSpec.Builder.setIsStrongBoxBacked()
- Импорт ключа:
KeyProtection.Builder.setIsStrongBoxBacked()
Если StrongBox KeyMint не поддерживает указанный алгоритм или размер ключа, платформа выдаст исключение StrongBoxUnavailableException
. В этом случае сгенерируйте или импортируйте ключ без вызова setIsStrongBoxBacked(true)
.
Разрешения на использование ключей
Чтобы избежать несанкционированного использования ключей на устройстве Android, Android Keystore позволяет приложениям указывать авторизованное использование своих ключей при их создании или импорте. После того как ключ сгенерирован или импортирован, его полномочия нельзя изменить. Затем авторизация применяется хранилищем ключей Android при каждом использовании ключа. Это расширенная функция безопасности, которая обычно полезна только в том случае, если вы хотите, чтобы компрометация процесса вашего приложения после генерации/импорта ключа (но не до или во время) не могла привести к несанкционированному использованию ключа.
Поддерживаемые разрешения на использование ключей делятся на следующие категории:
- Криптография: ключ может использоваться только с авторизованными ключевыми алгоритмами, операциями или целями (шифровать, дешифровать, подписывать, проверять), схемами заполнения, режимами блокировки или дайджестами.
- Временной интервал действия: ключ разрешен к использованию только в течение определенного интервала времени.
- Аутентификация пользователя: ключ можно использовать только в том случае, если пользователь прошел аутентификацию достаточно недавно. См. раздел Требование аутентификации пользователя для использования ключа .
В качестве дополнительной меры безопасности для ключей, материал ключа которых находится внутри защищенного оборудования (см. KeyInfo.isInsideSecurityHardware()
или, для приложений, предназначенных для Android 10 (уровень API 29) или выше, KeyInfo.getSecurityLevel()
), некоторые авторизации на использование ключей могут быть принудительно применены защищенным оборудованием, в зависимости от устройства Android. Безопасное оборудование обычно обеспечивает криптографическую авторизацию и аутентификацию пользователя. Однако защищенное оборудование обычно не обеспечивает авторизацию временного интервала действия, поскольку оно обычно не имеет независимых, безопасных часов реального времени.
Вы можете запросить, применяется ли авторизация аутентификации пользователя ключа безопасным оборудованием, используя KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()
.
Выбирайте между связкой ключей и поставщиком хранилища ключей Android.
Используйте KeyChain
API, если вам нужны общесистемные учетные данные. Когда приложение запрашивает использование каких-либо учетных данных через 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 расшифровывает ключи в хранилище ключей, поэтому содержимое ключей никогда не отображается в виде открытого текста в памяти хоста устройства. Этот процесс обеспечивает дополнительную безопасность расшифровки ключа.
Чтобы обеспечить безопасный импорт зашифрованных ключей в хранилище ключей, выполните следующие действия:
Создайте пару ключей, которая использует цель
PURPOSE_WRAP_KEY
. Мы рекомендуем вам также добавить аттестацию к этой паре ключей.На сервере или компьютере, которому вы доверяете, сгенерируйте сообщение 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 }
Создайте объект
WrappedKeyEntry
, передав сообщение ASN.1 в виде массива байтов.Передайте этот объект
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()
.
Узнайте больше о том, как добавить возможности биометрической аутентификации в ваше приложение, в том числе о том, как отобразить диалоговое окно биометрической аутентификации .
Поддерживаемые алгоритмы
-
Cipher
-
KeyGenerator
-
KeyFactory
-
KeyStore
(поддерживает те же типы ключей, что иKeyGenerator
иKeyPairGenerator
) -
KeyPairGenerator
-
Mac
-
Signature
-
SecretKeyFactory
Что дальше
- Ознакомьтесь с унификацией доступа к хранилищу ключей в ICS в блоге разработчиков Android.