Делая свое приложение более безопасным, вы помогаете сохранить доверие пользователей и целостность устройства.
На этой странице представлены несколько рекомендаций, которые оказывают значительное положительное влияние на безопасность вашего приложения.
Обеспечьте безопасную связь
Когда вы защищаете данные, которыми вы обмениваетесь между своим приложением и другими приложениями или между вашим приложением и веб-сайтом, вы повышаете стабильность своего приложения и защищаете данные, которые вы отправляете и получаете.
Защитите связь между приложениями
Чтобы обеспечить более безопасную связь между приложениями, используйте неявные намерения с помощью средства выбора приложений, разрешений на основе подписей и поставщиков неэкспортируемого контента.
Показать средство выбора приложения
Если неявное намерение может запустить как минимум два возможных приложения на устройстве пользователя, явно покажите средство выбора приложения. Эта стратегия взаимодействия позволяет пользователям передавать конфиденциальную информацию в приложение, которому они доверяют.
Котлин
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();
Добавьте конфигурацию сетевой безопасности
Если ваше приложение использует новые или пользовательские центры сертификации, вы можете объявить параметры безопасности вашей сети в файле конфигурации. Этот процесс позволяет создать конфигурацию без изменения кода приложения.
Чтобы добавить файл конфигурации сетевой безопасности в ваше приложение, выполните следующие действия:
- Объявите конфигурацию в манифесте вашего приложения:
Добавьте файл ресурсов 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>
<manifest ... > <application android:networkSecurityConfig="@xml/network_security_config" ... > <!-- Place child elements of <application> element here. --> </application> </manifest>
Информация, связанная с данной: Конфигурация сетевой безопасности.
Создайте своего собственного доверительного управляющего
Ваша программа проверки 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://
, а не URIfile://
. Экземпляры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 .
- На наличие сторонних зависимостей проверьте веб-сайты библиотек, которые использует ваше приложение, и установите все доступные обновления и исправления безопасности.
Информация по теме: Добавление зависимостей сборки
Дополнительная информация
Чтобы узнать больше о том, как сделать ваше приложение более безопасным, просмотрите следующие ресурсы:
- Основной контрольный список безопасности качества приложения
- Программа улучшения безопасности приложений
- Канал разработчиков Android на YouTube
- Лаборатория кода по настройке сетевой безопасности Android
- Android Protected Confirmation: безопасность транзакций выходит на новый уровень