Гостевая статья Шифрование данных на Android с помощью Jetpack Security

по.png


Вы когда-нибудь пытались зашифровать данные в своем приложении? Как разработчик, вы хотите, чтобы данные были в безопасности, и в руках стороны, имеющей право на их использование. Но если вы как большинство разработчиков Android, и у вас нет специальной группы безопасников, которая бы помогала правильно шифровать данные вашего приложения. Поискав в Интернете, как зашифровать данные, вы можете получить ответы, которые устарели на несколько лет и сделать неправильные выводы..

Крипто библиотека Jetpack Security (JetSec) предоставляет абстракции для шифрования объектов Files и SharedPreferences. Библиотека способствует использованию AndroidKeyStore при использовании безопасных и известных криптографических примитивов. Использование EncryptedFile и EncryptedSharedPreferences позволяет локально защитить файлы, которые могут содержать конфиденциальные данные, ключи API, токены OAuth и другие типы секретов.

Почему вы хотите зашифровать данные в вашем приложении? Разве Android, начиная с 5.0, не шифрует содержимое раздела данных пользователя по умолчанию? Конечно, это так, но есть некоторые случаи использования, когда вам может потребоваться дополнительный уровень защиты. Если ваше приложение использует общее хранилище, вы должны зашифровать данные. В домашнем каталоге приложения ваше приложение должно шифровать данные, если ваше приложение обрабатывает конфиденциальную информацию, включая, помимо прочего, информацию, позволяющую установить личность (PII), медицинские записи, финансовые данные или корпоративные данные. По возможности мы рекомендуем привязывать эту информацию к биометрии для дополнительного уровня защиты.

Jetpack Security основан на Tink, кроссплатформенном проекте безопасности с открытым исходным кодом от Google. Тинк может подойти, если вам нужно общее шифрование, гибридное шифрование или что-то подобное. Структуры данных Jetpack Security полностью совместимы с Tink.

Генерация ключей

Прежде чем мы начнем шифровать ваши данные, важно понять, как ваши ключи шифрования будут храниться в безопасности. Jetpack Security использует master key, который шифрует все subkeys, которые используются для каждой криптографической операции. JetSec предоставляет рекомендуемый master key по умолчанию в классе MasterKeys. Этот класс использует базовый ключ AES256-GCM, который генерируется и хранится в AndroidKeyStore. AndroidKeyStore - это контейнер, который хранит криптографические ключи в TEE или StrongBox, что затрудняет их извлечение. Subkeys хранятся в настраиваемом объекте SharedPreferences.

В первую очередь мы используем спецификацию AES256_GCM_SPEC в Jetpack Security, которая рекомендуется для общих случаев использования. AES256-GCM является симметричным и в целом быстро работает на современных устройствах.

Код:
val keyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

Для приложений, которые требуют более тщательной настройки или обрабатывают очень конфиденциальные данные, рекомендуется создать KeyGenParameterSpec, выбирая варианты, которые имеют смысл для вашего использования. Ключи с привязкой по времени с BiometricPrompt могут обеспечить дополнительный уровень защиты от рутированных или скомпрометированных устройств.

Важные опции:

  • userAuthenticationRequired () и userAuthenticationValiditySeconds () могут использоваться для создания ключа с привязкой ко времени. Ключи с привязкой ко времени требуют авторизации с использованием BiometricPrompt для шифрования и дешифрования симметричных ключей.
  • unlockedDeviceRequired () устанавливает флаг, который помогает предотвратить доступ к ключу, если устройство не разблокировано. Этот флаг доступен на Android Pie и выше.
  • Используйте setIsStrongBoxBacked (), чтобы выполнять криптографические операции на более производительном отдельном чипе. Это оказывает небольшое влияние на производительность, но является более безопасным. Он доступен на некоторых устройствах с Android Pie или выше.
Примечание

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

Код:
// Кастомный master key
val advancedSpec = KeyGenParameterSpec.Builder(
    "master_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
    setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    setKeySize(256)
    setUserAuthenticationRequired(true)
    setUserAuthenticationValidityDurationSeconds(15) // must be larger than 0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        setUnlockedDeviceRequired(true)
        setIsStrongBoxBacked(true)
    }
}.build()

val advancedKeyAlias = MasterKeys.getOrCreate(advancedSpec)

Разблокировка привязанных по времени ключей

Вы должны использовать BiometricPrompt для авторизации устройства, если ваш ключ был создан со следующими параметрами:

  • userAuthenticationRequired true
  • userAuthenticationValiditySeconds > 0

После аутентификации пользователя ключи разблокируются на время, установленное в поле validity seconds . AndroidKeystore не имеет API для запроса настроек ключа, поэтому ваше приложение должно отслеживать эти настройки. Вы должны создать свой экземпляр BiometricPrompt в методе onCreate () в activity, в котором вы представляете диалоговое окно пользователю.

Код BiometricPrompt для разблокировки ключей с привязкой ко времени

Код:
// Activity.onCreate

val promptInfo = PromptInfo.Builder()
    .setTitle("Разблокировать")
    .setDescription("Вы желаете разблокировать этот ключ?")
    .setDeviceCredentialAllowed(true)
    .build()

val biometricPrompt = BiometricPrompt(
    this, // Activity
    ContextCompat.getMainExecutor(this),
    authenticationCallback
)

private val authenticationCallback = object : AuthenticationCallback() {
        override fun onAuthenticationSucceeded(
            result: AuthenticationResult
        ) {
            super.onAuthenticationSucceeded(result)
            // Unlocked -- do work here.
        }
        override fun onAuthenticationError(
            errorCode: Int, errString: CharSequence
        ) {
            super.onAuthenticationError(errorCode, errString)
            // Handle error.
        }
    }

To use:
biometricPrompt.authenticate(promptInfo)

Шифруем файлы

Jetpack Security включает класс EncryptedFile, который устраняет проблемы, связанные с шифрованием файловых данных. Подобно File, EncryptedFile предоставляет объект FileInputStream для чтения и объект FileOutputStream для записи. Файлы шифруются с использованием потоковой передачи AEAD, которая соответствует определению OAE2. Данные разделяются на куски и шифруются с использованием AES256-GCM таким образом, что невозможно изменить их порядок.

Код:
val secretFile = File(filesDir, "super_secret")
val encryptedFile = EncryptedFile.Builder(
    secretFile,
    applicationContext,
    advancedKeyAlias,
    FileEncryptionScheme.AES256_GCM_HKDF_4KB)
    .setKeysetAlias("file_key") // опционально
    .setKeysetPrefName("secret_shared_prefs") // опционально
    .build()

encryptedFile.openFileOutput().use { outputStream ->
    // Записываем данные в наш зашифрованный файл

encryptedFile.openFileInput().use { inputStream ->
    // Считываем данные из нашего зашифрованного файла
}

Шифруем SharedPreferences

Если вашему приложению необходимо сохранить пары ключ-значение - например, ключи API - JetSec предоставляет класс EncryptedSharedPreferences, который использует тот же интерфейс SharedPreferences, к которому вы привыкли.

И ключи, и значения зашифрованы. Ключи шифруются с использованием AES256-SIV-CMAC, который обеспечивает детерминированный шифротекст; значения зашифрованы с помощью AES256-GCM и привязаны к зашифрованному ключу. Эта схема позволяет безопасно шифровать данные ключа, в то же время разрешая поиск.

Код:
EncryptedSharedPreferences.create(
    "my_secret_prefs",
    advancedKeyAlias,
    applicationContext,
    PrefKeyEncryptionScheme.AES256_SIV,
    PrefValueEncryptionScheme.AES256_GCM
).edit {
    // Update secret values
}


 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!