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

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

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

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

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

Защитите связь между приложениями

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

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

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

Котлин

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)
}

Ява

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-трафик

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

Котлин

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

Ява

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 в вашем приложении не должны позволять пользователям переходить на сайты, находящиеся вне вашего контроля. По возможности используйте список разрешений, чтобы ограничить контент, загружаемый объектами 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"))

Ява

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"));

Связанная информация:

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

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

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

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

В следующем примере показано, как использовать намерение для направления пользователей в приложение контактов вместо запроса разрешений 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)
    }
}

Ява

// 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:// , а не URI 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)
    }
}

Ява

// 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)
}

Ява

// 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"
    }
}

Ява

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) }
    }
}

Ява

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)
}

Ява

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 . Вместо этого следуйте инструкциям по безопасному обмену данными между приложениями .

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

Связанная информация:

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

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

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

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

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

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

Связанная информация:

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

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

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

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

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

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

,

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

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

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

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

Защитите связь между приложениями

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

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

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

Котлин

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)
}

Ява

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-трафик

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

Котлин

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

Ява

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 в вашем приложении не должны позволять пользователям переходить на сайты, находящиеся вне вашего контроля. По возможности используйте список разрешений, чтобы ограничить контент, загружаемый объектами 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"))

Ява

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"));

Связанная информация:

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

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

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

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

В следующем примере показано, как использовать намерение для направления пользователей в приложение контактов вместо запроса разрешений 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)
    }
}

Ява

// 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:// , а не URI 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)
    }
}

Ява

// 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)
}

Ява

// 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"
    }
}

Ява

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) }
    }
}

Ява

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)
}

Ява

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 . Вместо этого следуйте инструкциям по безопасному обмену данными между приложениями .

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

Связанная информация:

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

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

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

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

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

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

Связанная информация:

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

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

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

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

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

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

,

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

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

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

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

Защитите связь между приложениями

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

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

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

Котлин

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)
}

Ява

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-трафик

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

Котлин

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

Ява

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 в вашем приложении не должны позволять пользователям переходить на сайты, находящиеся вне вашего контроля. По возможности используйте список разрешений, чтобы ограничить контент, загружаемый объектами 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"))

Ява

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"));

Связанная информация:

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

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

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

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

В следующем примере показано, как использовать намерение для направления пользователей в приложение контактов вместо запроса разрешений 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)
    }
}

Ява

// 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:// , а не URI 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)
    }
}

Ява

// 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)
}

Ява

// 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"
    }
}

Ява

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, в зависимости от вашего варианта использования:

Проверьте наличие объема хранения

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

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

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

Следующий фрагмент кода включает в себя пример хэш -проверки:

Котлин

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) }
    }
}

Ява

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)
}

Ява

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 . Вместо этого следуйте шагам, чтобы безопасно обмениваться данными по приложениям .

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

Связанная информация:

Сохранять услуги и зависимости в курсе

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

Проверьте поставщика безопасности Google Play Services Services

ПРИМЕЧАНИЕ. Этот раздел применяется только к приложениям, нацеленным на устройства, на которых установлены службы Google Play .

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

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

Связанная информация:

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

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

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

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

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

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

,

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

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

Обеспечить безопасное общение

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

Защита общения между приложениями

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

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

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

Котлин

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)
}

Ява

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

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

Котлин

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

Ява

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

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

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

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

  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> для явного разрешения на установленных пользователей сертификатов. Этот элемент переопределяет варианты безопасности вашего приложения во время отладки и тестирования, не влияя на конфигурацию выпуска приложения. Следующий фрагмент показывает, как определить этот элемент в файле конфигурации сети сетевой безопасности вашего приложения:

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

Связанная информация: конфигурация безопасности сети

Создайте свой собственный менеджер доверия

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

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

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

Связанная информация:

Тщательно используйте объекты WebView

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

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

Используйте каналы сообщений HTML

Если ваше приложение должно использовать поддержку интерфейса javaScript на устройствах под управлением Android 6.0 (уровень 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"))

Ява

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"));

Связанная информация:

Обеспечить правильные разрешения

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

Используйте намерения отложить разрешения

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

В следующем примере показано, как использовать намерение направить пользователей в приложение Contacts вместо запроса разрешений 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)
    }
}

Ява

// 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 .
  • При обмене данными используйте content:// uris, а не file:// uris. Экземпляры FileProvider делают это для вас.

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

Котлин

// 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)
    }
}

Ява

// 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)
}

Ява

// 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"
    }
}

Ява

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, в зависимости от вашего варианта использования:

Проверьте наличие объема хранения

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

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

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

Следующий фрагмент кода включает в себя пример хэш -проверки:

Котлин

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) }
    }
}

Ява

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)
}

Ява

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 . Вместо этого следуйте шагам, чтобы безопасно обмениваться данными по приложениям .

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

Связанная информация:

Сохранять услуги и зависимости в курсе

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

Проверьте поставщика безопасности Google Play Services Services

ПРИМЕЧАНИЕ. Этот раздел применяется только к приложениям, нацеленным на устройства, на которых установлены службы Google Play .

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

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

Связанная информация:

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

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

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

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

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

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