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

Эта статья предназначена для ознакомления с передовыми методами обеспечения безопасности Android и размышлений о том, что может пойти не так.

Уже занимаетесь разработкой приложений для Android? Самые продвинутые из них вы найдете в конце этой статьи и в будущих обзорах!

Отправная точка

Мы начнем наше путешествие по безопасности приложений Android с минимальной настройки. Приложение уже использует подключения только по протоколу HTTPS (или WSS в случае WebSocket), а серверная часть защищена сама по себе.

Что может пойти не так? Много вещей!

Прежде всего, убедитесь, что зависимости не используют простой трафик HTTP. Вы можете легко проверить это, проанализировав окончательный APK, созданный в вашей сборке выпуска, особенно проверив окончательный файл AndroidManifest.xml. Если какой-либо библиотеке требуется использование простого HTTP, вы заметите что-то вроде следующего фрагмента кода:

<application android:usesCleartextTraffic="true">
  <!-- ... -->
</application>

<!-- Or also... -->

<application android:networkSecurityConfig="@xml/network_security_config">
  <!-- ... -->
</application>

В случае networkSecurityConfig вам также следует проверить этот конкретный файл конфигурации, но идея в этом. Как правило, не доверяйте коду библиотеки и всегда перепроверяйте окончательный APK.

Кроме того, вы можете использовать другие инструменты, такие как MobSF, но давайте продолжим!

Базовый уровень: обфускация кода и игровая подпись

Помимо преимуществ, которые мы уже должны знать об использовании таких инструментов, как R8 и ProGuard, таких как повышение производительности и уменьшение размера приложения за счет встряхивания дерева (и не только!), мы в основном фокусируемся на аспектах безопасности.

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

Наконец, Play Signing защищает нас от утечек хранилища ключей в таких местах, как репозиторий GitHub или небезопасное хранилище, а также со слабым паролем. Учитывая, что это растущая тенденция, как никогда важно принять все контрмеры.

Средний уровень: Jetpack Security

Библиотека Jetpack Security является частью Android Jetpack, набора библиотек, помогающих разработчикам создавать более качественные и надежные нативные приложения для Android, созданные Google.

Рассмотрим следующий пример: приложение Foo хранит маркеры доступа в виде открытого текста в SharedPreferences, надежное тем, что они находятся в частной папке данных приложения, и никто никогда не может получить к ним доступ.

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

Как мы только что сказали, Jetpack Security — наш друг! Используя эту замечательную библиотеку, вы можете быть уверены, что будете следовать всем передовым методам хранения неактивных данных. Обратите внимание, что он охватывает только шифрование данных в состоянии покоя; если вы хотите узнать больше о защите данных в сети, ознакомьтесь с Укрепление приложений Android: защита от MITM и подделки

Интеграция Jetpack Security в приведенном выше примере не требует усилий: нам нужно только изменить экземпляр SharedPreferences всякий раз, когда мы его используем, и интерфейс, предоставляемый Security, останется прежним!

Обратите внимание, что класс всегда реализует один и тот же интерфейс SharedPreferences!

// Old plaintext implementation
val sharedPreferences: SharedPreferences = context.getSharedPreferences(
  Constants.SHARED_PREFERENCES_FILE,
  Context.MODE_PRIVATE
);

// Encrypted implementation
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
  Constants.SHARED_PREFERENCES_FILE,
  MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
  context,
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

Вы можете ознакомиться с моим проектом, использующим Dagger for DI, и посмотреть, насколько гладко можно заменить реализацию SharedPreferences открытым текстом, при условии, что ваша кодовая база была хорошо спроектирована. В приведенном ниже примере класс даже не знает реализации, ему просто требуется экземпляр, как и должно быть.

https://github.com/simonesestito/shops-queue-android/blob/4923023cc153af8b2ca06f99a36d1f3857abc76b/app/src/main/java/com/simonesestito/shopsqueue/di/module/SharedPreferencesModule.java#L50

MITM-защита: закрепление TLS

Клиенты всегда должны использовать HTTPS в первую очередь при подключении к удаленному серверу. Многие думают, что этого достаточно, но этого далеко не достаточно.

Разве это не решенная проблема в HTTPS? Злоумышленник может установить пользовательский корневой центр сертификации (центр сертификации) и с помощью службы VPN может успешно выполнять атаки MITM на наше приложение.

Он сможет заменить сервер в HTTPS-соединении из нашего приложения (используя службу VPN) и по-прежнему обмениваться данными в действительном TLS, поскольку он будет использовать самозаверяющий сертификат из ранее установленного корневого центра сертификации, который теперь распознается системой как действительный.

Что мы можем сделать? Как мы только что сказали, сертификат меняется! Не существует единого способа защиты: кто-то может принудительно применить определенный сертификат и принять подключение только и только в том случае, если предоставленный сертификат с сервера является единственным, который ожидает приложение. Однако это не рекомендуется, поскольку изменение этого сертификата приведет к невозможности обмена данными.

Как реализовать закрепление сертификата

Самый простой и распространенный способ сделать это — использовать OkHttp и его CertificatePinner. Это гарантирует, что полученный сертификат имеет тот же самый хэш, жестко запрограммированный внутри приложения.

val certificatePinner = CertificatePinner.Builder()
    .add(
        "example.com",
        "sha256/71fsRfHxkGjHCpJNew0CmCO97dMTQN46rCCQZW+xk/I="
    )
    .build()
val okHttpClient = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

Обратите внимание, что, поскольку Retrofit основан на OkHttp, это решение также будет работать на нем. Передача client() в Retrofit.Builder сделает работу выполненной.

Защита от SSL-эксплойтов

Android использует класс Provider из Java для защиты сетевых подключений. Уязвимости в OpenSSL могут быть обнаружены, и ваше приложение всегда должно быть уверено, что эти недостатки будут исправлены как можно скорее.

К счастью, в Сервисе Google Play есть решение.

Используя ProviderInstaller, мы можем установить новые исправления, если таковые имеются, и оставаться в безопасности. Однако установка исправления может завершиться ошибкой, и в этом случае приложение должно рассматривать любое HTTPS-соединение как обычное HTTP, поскольку нельзя делать никаких предположений о безопасности соединения.

Поскольку эта статья представляет собой общий обзор этих тем, для получения дополнительных инструкций о том, как применять исправления с помощью ProviderInstaller, вы можете обратиться к официальной документации. Как правило, вы можете запустить асинхронную установку в MainActivity в событиях жизненного цикла onCreate() и onPostResume().

class MainActivity : Activity(), ProviderInstaller.ProviderInstallListener {

  // This is a general overview!

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Call the async installation
    ProviderInstaller.installIfNeededAsync(this, this)
  }

  override fun onProviderInstalled() {
    // Only from this point,
    // you are safe to perform secure network requests!
  }
}

Продвинутый уровень: Play Integrity API

В предыдущих предложениях мы защищали клиент, но не сервер!

Этот API является новой мощной заменой SafetyNet и позволяет внутреннему серверу узнать, является ли устройство, с которым он взаимодействует, законным или, возможно, модифицированной версией (если приложение вообще!)

Однако это сопряжено с много сложностей, поэтому я написал отдельную статью для этого. Проверьте это!

Не забудьте подписаться на меня в LinkedIn, если вам понравилась эта статья!