Повысьте безопасность вашего приложения

Повышая безопасность своего приложения, вы помогаете сохранить доверие пользователей и целостность устройств.

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

Обеспечьте безопасную связь

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

Обеспечьте безопасность обмена данными между приложениями.

Для более безопасного взаимодействия между приложениями используйте неявные намерения с выбором приложения, разрешения на основе подписи и неэкспортируемые поставщики контента.

Показать окно выбора приложений

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

Котлин

val intent = Intent(Intent.ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
        packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    val chooser = resources.getString(R.string.chooser_title).let { title ->
        Intent.createChooser(intent, title)
    }
    startActivity(chooser)
} else if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> possibleActivitiesList = getPackageManager()
        .queryIntentActivities(intent, PackageManager.MATCH_ALL);

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size() > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    String title = getResources().getString(R.string.chooser_title);
    Intent chooser = Intent.createChooser(intent, title);
    startActivity(chooser);
} else if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

Дополнительная информация:

Примените разрешения на основе подписи.

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <permission android:name="my_custom_permission_name"
                android:protectionLevel="signature" />

Дополнительная информация:

Запретите доступ к поставщикам контента вашего приложения.

Если вы не собираетесь отправлять данные из своего приложения в другое приложение, которое вам не принадлежит, явно запретите приложениям других разработчиков доступ к объектам ContentProvider вашего приложения. Этот параметр особенно важен, если ваше приложение можно установить на устройствах под управлением Android 4.1.1 (уровень API 16) или ниже, поскольку атрибут android:exported элемента <provider> по умолчанию имеет true в этих версиях Android.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application ... >
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            ...
            android:exported="false">
            <!-- Place child elements of <provider> here. -->
        </provider>
        ...
    </application>
</manifest>

Перед показом конфиденциальной информации запросите подтверждение ваших полномочий.

При запросе у пользователей учетных данных для доступа к конфиденциальной информации или платному контенту в вашем приложении, запросите либо PIN-код/пароль/графический ключ, либо биометрические данные, такие как распознавание лица или отпечатков пальцев.

Чтобы узнать больше о том, как запросить биометрические данные, ознакомьтесь с руководством по биометрической аутентификации .

Примените меры сетевой безопасности.

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

Используйте TLS-трафик

Если ваше приложение взаимодействует с веб-сервером, имеющим сертификат, выданный известным и доверенным центром сертификации (ЦС), используйте HTTPS-запрос следующего вида:

Котлин

val url = URL("https://www.google.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
urlConnection.inputStream.use {
    ...
}

Java

URL url = new URL("https://www.google.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();
InputStream in = urlConnection.getInputStream();

Добавьте конфигурацию сетевой безопасности.

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

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

  1. Укажите конфигурацию в манифесте вашего приложения:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
  3. Добавьте XML-файл ресурсов, расположенный по адресу res/xml/network_security_config.xml .

    Укажите, что весь трафик к определенным доменам должен использовать HTTPS, отключив передачу данных в открытом виде:

    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">secure.example.com</domain>
            ...
        </domain-config>
    </network-security-config>

    В процессе разработки вы можете использовать элемент <debug-overrides> , чтобы явно разрешить установку сертификатов пользователем. Этот элемент переопределяет критически важные для безопасности параметры вашего приложения во время отладки и тестирования, не влияя на конфигурацию выпуска приложения. Следующий фрагмент кода показывает, как определить этот элемент в XML-файле конфигурации сетевой безопасности вашего приложения:

    <network-security-config>
        <debug-overrides>
            <trust-anchors>
                <certificates src="user" />
            </trust-anchors>
        </debug-overrides>
    </network-security-config>

Дополнительная информация: Настройка сетевой безопасности

Создайте собственный менеджер доверительных средств.

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

  • Вы взаимодействуете с веб-сервером, сертификат которого подписан новым или пользовательским центром сертификации.
  • Данный центр сертификации не пользуется доверием используемого вами устройства.
  • Использовать конфигурацию сетевой безопасности невозможно.

Чтобы узнать больше о том, как выполнить эти шаги, см. обсуждение обработки неизвестного центра сертификации .

Дополнительная информация:

Используйте объекты WebView с осторожностью.

Объекты WebView в вашем приложении не должны позволять пользователям переходить на сайты, которые находятся вне вашего контроля. По возможности используйте список разрешенных сайтов (allowlist), чтобы ограничить контент, загружаемый объектами WebView вашего приложения.

Кроме того, никогда не включайте поддержку интерфейса JavaScript, если вы не полностью контролируете и не доверяете содержимому объектов WebView вашего приложения.

Используйте каналы обмена сообщениями в формате HTML.

Если вашему приложению необходимо использовать интерфейс JavaScript на устройствах под управлением Android 6.0 (уровень API 23) и выше, используйте каналы обмена сообщениями HTML вместо связи между веб-сайтом и вашим приложением, как показано в следующем фрагменте кода:

Котлин

val myWebView: WebView = findViewById(R.id.webview)

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel()

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() {

    override fun onMessage(port: WebMessagePort, message: WebMessage) {
        Log.d(TAG, "On port $port, received this message: $message")
    }
})

// Send a message from channel[1] to channel[0].
channel[1].postMessage(WebMessage("My secure message"))

Java

WebView myWebView = (WebView) findViewById(R.id.webview);

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
WebMessagePort[] channel = myWebView.createWebMessageChannel();

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
    @Override
    public void onMessage(WebMessagePort port, WebMessage message) {
         Log.d(TAG, "On port " + port + ", received this message: " + message);
    }
});

// Send a message from channel[1] to channel[0].
channel[1].postMessage(new WebMessage("My secure message"));

Дополнительная информация:

Предоставьте необходимые права доступа.

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

Используйте интенты для отсрочки разрешений.

По возможности не добавляйте в приложение разрешение на выполнение действия, которое может быть выполнено в другом приложении. Вместо этого используйте Intent для переадресации запроса другому приложению, которое уже имеет необходимое разрешение.

В следующем примере показано, как использовать Intent для перенаправления пользователей в приложение «Контакты» вместо запроса разрешений READ_CONTACTS и WRITE_CONTACTS :

Котлин

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent(Intent.ACTION_INSERT).apply {
    type = ContactsContract.Contacts.CONTENT_TYPE
}.also { intent ->
    // Make sure that the user has a contacts app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

Java

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent insertContactIntent = new Intent(Intent.ACTION_INSERT);
insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);

// Make sure that the user has a contacts app installed on their device.
if (insertContactIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(insertContactIntent);
}

Кроме того, если вашему приложению необходимо выполнять операции ввода-вывода на основе файлов — например, обращаться к хранилищу или выбирать файл — ему не требуются специальные разрешения, поскольку система может выполнить эти операции от имени вашего приложения. Более того, после того, как пользователь выберет контент по определенному URI, вызывающему приложению предоставляется разрешение на доступ к выбранному ресурсу.

Дополнительная информация:

Безопасный обмен данными между приложениями.

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

  • При необходимости установите права доступа только для чтения или только для записи.
  • Предоставьте клиентам одноразовый доступ к данным, используя флаги FLAG_GRANT_READ_URI_PERMISSION и FLAG_GRANT_WRITE_URI_PERMISSION .
  • При обмене данными используйте URI content:// , а не file:// . Экземпляры FileProvider делают это за вас.

Приведенный ниже фрагмент кода демонстрирует, как использовать флаги предоставления разрешений URI и разрешения поставщика контента для отображения PDF-файла приложения в отдельном приложении для просмотра PDF-файлов:

Котлин

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("content://com.example/personal-info.pdf")

    // This flag gives the started app read access to the file.
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.also { intent ->
    // Make sure that the user has a PDF viewer app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

Java

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW);
viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf"));

// This flag gives the started app read access to the file.
viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// Make sure that the user has a PDF viewer app installed on their device.
if (viewPdfIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(viewPdfIntent);
}

Примечание: Выполнение файлов из каталога приложения, доступного для записи, является нарушением W^X . По этой причине ненадежные приложения, ориентированные на Android 10 (уровень API 29) и выше, не могут вызывать exec() для файлов в каталоге приложения, только для двоичного кода, встроенного в APK-файл приложения. Кроме того, приложения, ориентированные на Android 10 и выше, не могут в памяти изменять исполняемый код файлов, открытых с помощью dlopen() . Это включает в себя любые разделяемые объектные файлы ( .so ) с перемещением текста.

Дополнительная информация: android:grantUriPermissions

Храните данные в безопасном месте.

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

Храните конфиденциальные данные во внутренней памяти.

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

Приведенный ниже фрагмент кода демонстрирует один из способов записи данных во внутреннюю память:

Котлин

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
val FILE_NAME = "sensitive_info.txt"
val fileContents = "This is some top-secret information!"
File(filesDir, FILE_NAME).bufferedWriter().use { writer ->
    writer.write(fileContents)
}

Java

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
final String FILE_NAME = "sensitive_info.txt";
String fileContents = "This is some top-secret information!";
try (BufferedWriter writer =
             new BufferedWriter(new FileWriter(new File(getFilesDir(), FILE_NAME)))) {
    writer.write(fileContents);
} catch (IOException e) {
    // Handle exception.
}

Следующий фрагмент кода демонстрирует обратную операцию — чтение данных из внутренней памяти:

Котлин

val FILE_NAME = "sensitive_info.txt"
val contents = File(filesDir, FILE_NAME).bufferedReader().useLines { lines ->
    lines.fold("") { working, line ->
        "$working\n$line"
    }
}

Java

final String FILE_NAME = "sensitive_info.txt";
StringBuffer stringBuffer = new StringBuffer();
try (BufferedReader reader =
             new BufferedReader(new FileReader(new File(getFilesDir(), FILE_NAME)))) {

    String line = reader.readLine();
    while (line != null) {
        stringBuffer.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Handle exception.
}

Дополнительная информация:

Хранение данных во внешнем хранилище в зависимости от сценария использования.

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

Если файл не содержит личной или конфиденциальной информации, но представляет ценность для пользователя только в вашем приложении, сохраните его в каталоге, предназначенном для конкретного приложения, на внешнем накопителе .

Если вашему приложению необходимо получить доступ к файлу, представляющему ценность для других приложений, или сохранить его, используйте один из следующих API в зависимости от ваших задач:

  • Медиафайлы: Для хранения и доступа к изображениям, аудиофайлам и видео, которыми обмениваются приложения, используйте API Media Store .
  • Другие файлы: Для хранения и доступа к другим типам общих файлов, включая загруженные файлы, используйте Storage Access Framework .

Проверьте наличие свободного места для хранения данных.

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

Проверьте достоверность данных.

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

Приведённый ниже фрагмент кода содержит пример верификатора хеша:

Котлин

val hash = calculateHash(stream)
// Store "expectedHash" in a secure location.
if (hash == expectedHash) {
    // Work with the content.
}

// Calculating the hash code can take quite a bit of time, so it shouldn't
// be done on the main thread.
suspend fun calculateHash(stream: InputStream): String {
    return withContext(Dispatchers.IO) {
        val digest = MessageDigest.getInstance("SHA-512")
        val digestStream = DigestInputStream(stream, digest)
        while (digestStream.read() != -1) {
            // The DigestInputStream does the work; nothing for us to do.
        }
        digest.digest().joinToString(":") { "%02x".format(it) }
    }
}

Java

Executor threadPoolExecutor = Executors.newFixedThreadPool(4);
private interface HashCallback {
    void onHashCalculated(@Nullable String hash);
}

boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> {
    if (Objects.equals(hash, expectedHash)) {
        // Work with the content.
    }
});

if (!hashRunning) {
    // There was an error setting up the hash function.
}

private boolean calculateHash(@NonNull InputStream stream,
                              @NonNull Executor executor,
                              @NonNull HashCallback hashCallback) {
    final MessageDigest digest;
    try {
        digest = MessageDigest.getInstance("SHA-512");
    } catch (NoSuchAlgorithmException nsa) {
        return false;
    }

    // Calculating the hash code can take quite a bit of time, so it shouldn't
    // be done on the main thread.
    executor.execute(() -> {
        String hash;
        try (DigestInputStream digestStream =
                new DigestInputStream(stream, digest)) {
            while (digestStream.read() != -1) {
                // The DigestInputStream does the work; nothing for us to do.
            }
            StringBuilder builder = new StringBuilder();
            for (byte aByte : digest.digest()) {
                builder.append(String.format("%02x", aByte)).append(':');
            }
            hash = builder.substring(0, builder.length() - 1);
        } catch (IOException e) {
            hash = null;
        }

        final String calculatedHash = hash;
        runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash));
    });
    return true;
}

В кэш-файлы следует хранить только неконфиденциальные данные.

Для более быстрого доступа к неконфиденциальным данным приложения, храните их в кэше устройства. Для кэшей размером более 1 МБ используйте getExternalCacheDir() . Для кэшей размером 1 МБ или меньше используйте getCacheDir() . Оба метода предоставляют вам объект File , содержащий кэшированные данные вашего приложения.

Следующий фрагмент кода показывает, как кэшировать файл, который ваше приложение недавно загрузило:

Котлин

val cacheFile = File(myDownloadedFileUri).let { fileToCache ->
    File(cacheDir.path, fileToCache.name)
}

Java

File cacheDir = getCacheDir();
File fileToCache = new File(myDownloadedFileUri);
String fileToCacheName = fileToCache.getName();
File cacheFile = new File(cacheDir.getPath(), fileToCacheName);

Примечание: Если вы используете getExternalCacheDir() для размещения кэша вашего приложения в общем хранилище, пользователь может отключить носитель, содержащий это хранилище, во время работы приложения. Добавьте логику для корректной обработки промаха кэша, вызванного таким поведением пользователя.

Внимание: к этим файлам не применяется никаких мер безопасности. Поэтому любое приложение, ориентированное на Android 10 (уровень API 29) или ниже и имеющее разрешение WRITE_EXTERNAL_STORAGE , может получить доступ к содержимому этого кэша.

Дополнительная информация: Обзор хранения данных и файлов

Используйте SharedPreferences в приватном режиме.

При использовании getSharedPreferences() для создания или доступа к объектам SharedPreferences вашего приложения используйте MODE_PRIVATE . Таким образом, доступ к информации в файле общих настроек будет только у вашего приложения.

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

Дополнительная информация:

Поддерживайте актуальность сервисов и зависимостей.

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

Проверьте поставщика услуг безопасности Google Play.

Примечание: Этот раздел относится только к приложениям, предназначенным для устройств с установленными сервисами Google Play .

Если ваше приложение использует сервисы Google Play, убедитесь, что они обновлены на устройстве, где установлено приложение. Выполняйте проверку асинхронно, вне потока пользовательского интерфейса. Если устройство не обновлено, вызовите ошибку авторизации.

Чтобы определить, обновлены ли сервисы Google Play на устройстве, где установлено ваше приложение, выполните действия, описанные в руководстве по обновлению поставщика безопасности для защиты от уязвимостей SSL .

Дополнительная информация:

Обновите все зависимости приложения.

Перед развертыванием приложения убедитесь, что все библиотеки, SDK и другие зависимости обновлены:

  • Для обновления зависимостей от собственных компонентов, таких как Android SDK, используйте инструменты обновления, доступные в Android Studio, например, SDK Manager .
  • Для проверки зависимостей от сторонних разработчиков посетите веб-сайты библиотек, которые использует ваше приложение, и установите все доступные обновления и исправления безопасности.

Дополнительная информация: Добавление зависимостей сборки

Более подробная информация

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