Криптография

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

Указывайте поставщика только в системе Android Keystore

Если вы используете систему Android Keystore, то должны указать поставщика.

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

Как выбрать рекомендуемый алгоритм

Если вы можете выбрать алгоритм (например, когда вам не требуется совместимость со сторонней системой), рекомендуем использовать следующие алгоритмы:

Класс Рекомендация
Шифр AES в режиме CBC или GCM с 256-битными ключами (например, AES/GCM/NoPadding);
MessageDigest Семейство SHA-2 (например, SHA-256)
Mac HMAC семейства SHA-2 (например, HMACSHA256)
Подпись Семейство SHA-2 с ECDSA (например, SHA256withECDSA)

Выполнение стандартных криптографических операций.

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

Как зашифровать сообщение

Kotlin

val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv

Java

byte[] plaintext = ...;
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
SecretKey key = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(plaintext);
byte[] iv = cipher.getIV();

Как создать сводку сообщений

Kotlin

val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)

Java

byte[] message = ...;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(message);

Как создать цифровую подпись

Вам понадобится объект PrivateKey, содержащий ключ подписи. Его можно сгенерировать во время выполнения, прочитать из файла, связанного с приложением, или получить из другого источника в зависимости от ваших потребностей.

Kotlin

val message: ByteArray = ...
val key: PrivateKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initSign(key)
            update(message)
        }
val signature: ByteArray = s.sign()

Java

byte[] message = ...;
PrivateKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(key);
s.update(message);
byte[] signature = s.sign();

Как проверить цифровую подпись

Вам понадобится объект PublicKey, содержащий открытый ключ подписавшего. Его можно считать из файла, входящего в состав приложения, извлечь из сертификата или получить из другого источника.

Kotlin

val message: ByteArray = ...
val signature: ByteArray = ...
val key: PublicKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initVerify(key)
            update(message)
        }
val valid: Boolean = s.verify(signature)

Java

byte[] message = ...;
byte[] signature = ...;
PublicKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(key);
s.update(message);
boolean valid = s.verify(signature);

Сложности реализации

Некоторые детали реализации криптографии в Android могут показаться необычными, но они необходимы для обеспечения совместимости. В этом разделе рассматриваются те из них, с которыми вы, скорее всего, столкнетесь.

Сводка сообщения OAEP MGF1

Шифры RSA OAEP параметризуются двумя разными дайджестами сообщений: основным дайджестом и дайджестом MGF1. Существует Cipher идентификаторов, которые включают названия дайджестов, например Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), который указывает основной дайджест и оставляет дайджест MGF1 неопределенным. Для Android Keystore SHA-1 используется для дайджеста MGF1, а для других поставщиков криптографии Android два дайджеста совпадают.

Чтобы лучше контролировать дайджесты, которые использует ваше приложение, запросите шифр с OAEPPadding, как в Cipher.getInstance("RSA/ECB/OAEPPadding"), и укажите OAEPParameterSpec для init(), чтобы явно выбрать оба дайджеста. Это показано в коде ниже:

Kotlin

val key: Key = ...
val cipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
        .apply {
            // To use SHA-256 the main digest and SHA-1 as the MGF1 digest
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT))
            // To use SHA-256 for both digests
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT))
        }

Java

Key key = ...;
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
// To use SHA-256 the main digest and SHA-1 as the MGF1 digest
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT));
// To use SHA-256 for both digests
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));

Устаревшие функции

В разделах ниже описаны функции, поддержка которых прекращена. Не используйте его в своем приложении.

Алгоритмы Bouncy Castle

Реализации многих алгоритмов в Bouncy Castle устарели. Это касается только случаев, когда вы явно запрашиваете поставщика Bouncy Castle, как показано в примере ниже.

Kotlin

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC")
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))

Java

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"));

Как отмечалось в разделе Указание поставщика только с помощью системы Android Keystore, запрашивать определенного поставщика не рекомендуется. Если вы следуете этой рекомендации, то прекращение поддержки вас не затронет.

Шифры на основе паролей без вектора инициализации

Шифры на основе пароля, для которых требуется вектор инициализации, могут получить его из ключа, если он правильно создан, или из явно переданного вектора инициализации. Если вы передаете ключ PBE, который не содержит IV, и не передаете явный IV, шифры PBE на Android в настоящее время предполагают, что IV равен нулю.

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

Kotlin

val key: SecretKey = ...
val cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))

Java

SecretKey key = ...;
Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

Поставщик криптографических услуг

Начиная с Android 9 (уровень API 28) поставщик Crypto JCA (Java Cryptography Architecture) больше не поддерживается. Если приложение запрашивает экземпляр поставщика Crypto, например вызывает метод, указанный ниже, возникает ошибка NoSuchProviderException.

Kotlin

SecureRandom.getInstance("SHA1PRNG", "Crypto")

Java

SecureRandom.getInstance("SHA1PRNG", "Crypto");

Библиотека Jetpack Security-Crypto

Поддержка всех API в библиотеке Jetpack security-crypto прекращена в стабильном выпуске версии 1.1.0. Новые версии этой библиотеки больше не выпускаются.

Аннотации об устаревании видны, если в файле build.gradle модуля вашего приложения есть любая из следующих зависимостей:

Яркий

dependencies {
    implementation "androidx.security:security-crypto:1.1.0"
    // or
    implementation "androidx.security:security-crypto-ktx:1.1.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.1.0")
    // or
    implementation("androidx.security:security-crypto-ktx:1.1.0")
}

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

Ниже перечислены идентификаторы алгоритмов JCA, которые поддерживаются на устройствах Android.