24 марта 2020
3 793
Android разработка, Безопасность
Безопасность, несомненно, важная часть любого мобильного приложения. К сожалению, обеспечить реальную безопасность – непростая задача. Недавно Google выпустил библиотеку «security-crypto» в составе компонентов Jetpack, чтобы облегчить разработку безопасных приложений:
В настоящий момент библиотека состоит из трёх основных частей:
Внутри AndroidX Security использует библиотеку Tink. Tink — это библиотека с открытым исходным кодом от Google, предоставляющая криптографические API.
Чтобы воспользоваться библиотекой, нужно добавить всего одну строчку в Gradle-файл вашего приложения:
1 | implementation `androidx.security:security-crypto:1.0.0-beta01` |
В настоящее время «security-crypto» находится в beta-режиме, однако на странице mvnrepository можно следить за выходом новых версий. Она действительно очень маленькая — после добавления библиотеки размер файла .apk увеличился всего на 11,9 Кб.
Чтобы использовать библиотеку, нужно задать minSdkVersion 23+, потому что в новом API операции KeyStore сделали более стабильными (в API 23 KeyPairGeneratorSpec был заменён на KeyGenParameterSpec).
MasterKeys — вспомогательный класс всего с одним публичным методом getOrCreate(…). Он позволяет создать мастер-ключ, а затем получить для него псевдоним. Проанализируем код:
1 2 3 4 5 6 7 8 9 10 | @NonNull public static String getOrCreate( @NonNull KeyGenParameterSpec keyGenParameterSpec) throws GeneralSecurityException, IOException { validate(keyGenParameterSpec); if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) { generateKey(keyGenParameterSpec); } return keyGenParameterSpec.getKeystoreAlias(); } |
Единственный параметр, KeyGenParameterSpec, позволяет задавать такие настройки, как алгоритм, режим шифрования, выравнивание и размер ключа. Значение по умолчанию, присвоенное MasterKeys.AES256_GCM_SPEC, создает следующий объект:
1 2 3 4 5 6 7 8 9 10 11 | @NonNull private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec( @NonNull String keyAlias) { KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(KEY_SIZE); return builder.build(); } |
Где keyAlias принимает значение _androidx_security_master_key_, а KEY_SIZE = 256.
EncryptedSharedPreferences — класс-обёртка для SharedPreferences. Он позволяет сохранять и читать значения, при этом шифрование и расшифровывание данных происходят уже внутри него. Посмотрим, как создаётся его экземпляр:
1 2 3 4 5 6 7 | EncryptedSharedPreferences.create( PREFS_FILENAME, masterKeyAlias, applicationContext, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) |
Второй параметр — это псевдоним, сгенерированный методом MasterKeys.getOrCreate(KeyGenParameterSpec). Последние два параметра задают схемы, используемые для шифрования ключей и значений. Сейчас единственное возможное значение для EncryptedSharedPreferences.PrefKeyEncryptionScheme — это AES256_SIV, а для EncryptedSharedPreferences.PrefValueEncryptionScheme — это AES256_GCM.
После этого мы можем использовать его как обычный объект SharedPreferences. Например, так выглядит сохранение String:
1 2 3 4 | encryptedPrefs.edit { putString(ENC_KEY, value) apply() } |
А так чтение сохранённого значения:
1 | val string = encryptedPrefs.getString(ENC_KEY, null) |
EncryptedFile позволяет легко зашифровывать данные с помощью FileInputStream и расшифровывать их с помощью FileOutputStream. Чтобы создать экземпляр EncryptedFile, нам нужно сделать следующее:
1 2 3 4 5 6 | EncryptedFile.Builder( file, applicationContext, masterKeyAlias, EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB ).build() |
Первый параметр — это объект File, задающий путь и название файла с зашифрованными данными. В моём случае код выглядит следующим образом:
Значение masterKeyAlias задаётся точно так же, как для EncryptedSharedPreferences. Последний параметр, fileEncryptionScheme, задаёт схему шифрования потока ввода/вывода. В данный момент единственное возможное значение — это AES256_GCM_HKDF_4KB. Привожу характеристики данной схемы:
Для иллюстрации Я решил загрузить файл README.md с помощью библиотеки OkHttp из репозитория с примерами (ссылка здесь), считать байты ответа из response.body!!.Bytes(), а затем сохранить файл, передав байты в следующий метод:
1 2 3 4 5 6 7 8 9 10 11 12 | private fun onFileDownloaded(bytes: ByteArray) { var encryptedOutputStream: FileOutputStream? = null try { encryptedOutputStream = encryptedFile.openFileOutput().apply { write(bytes) } } catch (e: Exception) { Log.e(TAG, "Could not open encrypted file", e) } finally { encryptedOutputStream?.close() } } |
А затем прочитать их:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private fun readFile(fileInput: () -> FileInputStream) { var fileInputStream: FileInputStream? = null try { fileInputStream = fileInput() val reader = BufferedReader(InputStreamReader(fileInputStream)) val stringBuilder = StringBuilder() reader.forEachLine { line -> stringBuilder.appendln(line) } result.text = stringBuilder.toString() } catch (e: Exception) { Log.e(TAG, "Error occurred when reading file", e) } finally { fileInputStream?.close() } } |
Чтобы зашифровать и прочитать скачанный файл, я вызвал:
1 |
Чтобы убедиться, что данные нечитабельны без расшифровки, загрузим файл следующим образом:
Примеры кода из статьи можно найти в репозитории по ссылке.
AndroidX Security ловко скрывает реализацию сложной логику безопасности, предоставляя разработчикам простые интерфейсы. С помощью этой библиотеки и нескольких строк кода можно сделать приложение безопаснее и избежать ситуации, когда разработчик забывает сконфигурировать что-то важное. Единственным недостатком является то, что разработанное приложение будет совместимо только с Android Marshmallow и более поздними версиями. Однако, учитывая ситуацию с KeyGenParameterSpec и KeyPairGeneratorSpec, это вполне разумный компромисс. Кроме того, согласно статистике от Google, всего четверть устройств в мире работают на Android 5 или более ранних версиях и их количество постоянно уменьшается. При этом не следует забывать, что шифрование SharedPreferences и файлов в проекте — лишь один из многих факторов, делающих приложение по-настоящему безопасным. Будем надеяться, что стабильная версия библиотеки будет выпущена Google в ближайшее время.
Оригинал статьи: тут
Как работать с негативными отзывами в Google Play
20 декабря 2023 493
Как разрабатывать безопасные Android-приложения
18 мая 2020 5 521
16 января 2018 17 054
Размещение библиотеки через JCenter и Maven Central при помощи Android Studio
19 февраля 2018 17 960