Zwiększając bezpieczeństwo aplikacji, pomagasz zachować zaufanie użytkowników i integralność urządzenia.
Na tej stronie znajdziesz kilka sprawdzonych metod, które mają znaczący, pozytywny wpływ na bezpieczeństwo aplikacji.
Wymuszanie bezpiecznej komunikacji
Zabezpieczając dane wymieniane między Twoją aplikacją a innymi aplikacjami lub między Twoją aplikacją a witryną, zwiększasz stabilność aplikacji i chronisz wysyłane i odbierane dane.
Zabezpieczanie komunikacji między aplikacjami
Aby bezpieczniej komunikować się między aplikacjami, używaj niejawnych intencji z selektorem aplikacji, uprawnień opartych na sygnaturze i nieeksportowanych dostawców treści.
Wyświetlanie selektora aplikacji
Jeśli niejawny zamiar może uruchomić co najmniej 2 aplikacje na urządzeniu użytkownika, wyświetl selektor aplikacji. Ta strategia interakcji umożliwia użytkownikom przesyłanie informacji poufnych do aplikacji, której ufają.
Kotlin
val intent = Intent(Intent.ACTION_SEND) val possibleActivitiesList: List<ResolveInfo> = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL) // Verify that an activity in at least two apps on the user's device // can handle the intent. Otherwise, start the intent only if an app // on the user's device can handle the intent. if (possibleActivitiesList.size > 1) { // Create intent to show chooser. // Title is something similar to "Share this photo with." val chooser = resources.getString(R.string.chooser_title).let { title -> Intent.createChooser(intent, title) } startActivity(chooser) } else if (intent.resolveActivity(packageManager) != null) { startActivity(intent) }
Java
Intent intent = new Intent(Intent.ACTION_SEND); List<ResolveInfo> possibleActivitiesList = getPackageManager() .queryIntentActivities(intent, PackageManager.MATCH_ALL); // Verify that an activity in at least two apps on the user's device // can handle the intent. Otherwise, start the intent only if an app // on the user's device can handle the intent. if (possibleActivitiesList.size() > 1) { // Create intent to show chooser. // Title is something similar to "Share this photo with." String title = getResources().getString(R.string.chooser_title); Intent chooser = Intent.createChooser(intent, title); startActivity(chooser); } else if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); }
Powiązane informacje:
Stosowanie uprawnień na podstawie podpisu
Podczas udostępniania danych między 2 aplikacjami, które kontrolujesz lub których jesteś właścicielem, używaj uprawnień opartych na sygnaturze. Te uprawnienia nie wymagają potwierdzenia przez użytkownika, a zamiast tego sprawdzają, czy aplikacje uzyskujące dostęp do danych są podpisane tym samym kluczem podpisu. Dlatego te uprawnienia zapewniają bardziej przejrzystą i bezpieczną obsługę.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <permission android:name="my_custom_permission_name" android:protectionLevel="signature" />
Powiązane informacje:
Blokowanie dostępu do dostawców treści w aplikacji
Jeśli nie zamierzasz wysyłać danych z aplikacji do innej aplikacji, która nie jest Twoją własnością, wyraźnie zabroń aplikacjom innych deweloperów dostępu do obiektów ContentProvider
Twojej aplikacji. To ustawienie jest szczególnie ważne, jeśli aplikację można zainstalować na urządzeniach z Androidem 4.1.1 (API na poziomie 16) lub starszym, ponieważ atrybut android:exported
elementu <provider>
jest domyślnie ustawiony na true
w tych wersjach Androida.
<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>
Proś o dane logowania przed wyświetleniem informacji poufnych
Gdy prosisz użytkowników o podanie danych logowania, aby mogli uzyskać dostęp do informacji poufnych lub treści premium w Twojej aplikacji, poproś o kod PIN, hasło lub wzór albo o dane biometryczne, takie jak rozpoznawanie twarzy lub odcisków palców.
Więcej informacji o tym, jak poprosić o dane biometryczne, znajdziesz w przewodniku po uwierzytelnianiu biometrycznym.
Stosowanie środków bezpieczeństwa sieci
W sekcjach poniżej opisujemy, jak możesz zwiększyć bezpieczeństwo sieci w aplikacji.
Używanie ruchu TLS
Jeśli aplikacja komunikuje się z serwerem internetowym, który ma certyfikat wydany przez znanego, zaufanego urzędu certyfikacji, użyj żądania HTTPS, takiego jak to poniżej:
Kotlin
val url = URL("https://www.google.com") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.connect() urlConnection.inputStream.use { ... }
Java
URL url = new URL("https://www.google.com"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.connect(); InputStream in = urlConnection.getInputStream();
Dodawanie konfiguracji zabezpieczeń sieci
Jeśli Twoja aplikacja używa nowych lub niestandardowych urzędów certyfikacji, możesz zadeklarować ustawienia bezpieczeństwa sieci w pliku konfiguracyjnym. Dzięki temu możesz utworzyć konfigurację bez modyfikowania kodu aplikacji.
Aby dodać do aplikacji plik konfiguracji zabezpieczeń sieci, wykonaj te czynności:
- Zadeklaruj konfigurację w manifeście aplikacji:
-
Dodaj plik zasobu XML znajdujący się w lokalizacji
res/xml/network_security_config.xml
.Określ, że cały ruch do określonych domen musi używać protokołu HTTPS, wyłączając tekst jawny:
<network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">secure.example.com</domain> ... </domain-config> </network-security-config>
Podczas procesu programowania możesz użyć elementu
<debug-overrides>
, aby wyraźnie zezwolić na certyfikaty zainstalowane przez użytkownika. Ten element zastępuje opcje krytyczne dla bezpieczeństwa aplikacji podczas debugowania i testowania bez wpływu na konfigurację wersji aplikacji. Poniższy fragment pokazuje, jak zdefiniować ten element w pliku XML konfiguracji zabezpieczeń sieci aplikacji:<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>
Powiązane informacje: konfiguracja zabezpieczeń sieci
Tworzenie własnego menedżera zaufania
Sprawdzanie TLS nie powinno akceptować każdego certyfikatu. Może być konieczne skonfigurowanie menedżera zaufania i obsługa wszystkich ostrzeżeń TLS, które pojawią się, jeśli w Twoim przypadku użycia spełniony jest jeden z tych warunków:
- Komunikujesz się z serwerem internetowym, który ma certyfikat podpisany przez nowy lub niestandardowy urząd certyfikacji.
- Ten urząd certyfikacji nie jest zaufany na urządzeniu, którego używasz.
- Nie możesz używać konfiguracji bezpieczeństwa sieci.
Więcej informacji o wykonywaniu tych czynności znajdziesz w rozdziale o obsłudze nieznanego urzędu certyfikacji.
Powiązane informacje:
Rozważne używanie obiektów WebView
WebView
Obiekty w aplikacji nie powinny umożliwiać użytkownikom przechodzenia do witryn, nad którymi nie masz kontroli. W miarę możliwości używaj listy dozwolonych, aby ograniczać treści wczytywane przez obiekty WebView
w aplikacji.
Ponadto nigdy nie włączaj obsługi interfejsu JavaScript, chyba że masz pełną kontrolę nad treściami w obiektach WebView
aplikacji i im ufasz.
Korzystanie z kanałów wiadomości HTML
Jeśli Twoja aplikacja musi korzystać z obsługi interfejsu JavaScript na urządzeniach z Androidem 6.0 (poziom interfejsu API 23) lub nowszym, używaj kanałów wiadomości HTML zamiast komunikacji między witryną a aplikacją, jak pokazano w tym fragmencie kodu:
Kotlin
val myWebView: WebView = findViewById(R.id.webview) // channel[0] and channel[1] represent the two ports. // They are already entangled with each other and have been started. val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel() // Create handler for channel[0] to receive messages. channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() { override fun onMessage(port: WebMessagePort, message: WebMessage) { Log.d(TAG, "On port $port, received this message: $message") } }) // Send a message from channel[1] to channel[0]. channel[1].postMessage(WebMessage("My secure message"))
Java
WebView myWebView = (WebView) findViewById(R.id.webview); // channel[0] and channel[1] represent the two ports. // They are already entangled with each other and have been started. WebMessagePort[] channel = myWebView.createWebMessageChannel(); // Create handler for channel[0] to receive messages. channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { @Override public void onMessage(WebMessagePort port, WebMessage message) { Log.d(TAG, "On port " + port + ", received this message: " + message); } }); // Send a message from channel[1] to channel[0]. channel[1].postMessage(new WebMessage("My secure message"));
Powiązane informacje:
Przyznawanie odpowiednich uprawnień
Proś tylko o minimalną liczbę uprawnień niezbędnych do prawidłowego działania aplikacji. W miarę możliwości zwalniaj uprawnienia, gdy aplikacja nie będzie ich już potrzebować.
Używanie intencji do odraczania uprawnień
Jeśli to możliwe, nie dodawaj do aplikacji uprawnień, aby wykonać działanie, które można wykonać w innej aplikacji. Zamiast tego użyj intencji, aby przekazać żądanie do innej aplikacji, która ma już niezbędne uprawnienia.
Poniższy przykład pokazuje, jak użyć intencji, aby przekierować użytkowników do aplikacji do kontaktów zamiast prosić o uprawnienia READ_CONTACTS
i WRITE_CONTACTS
:
Kotlin
// Delegates the responsibility of creating the contact to a contacts app, // which has already been granted the appropriate WRITE_CONTACTS permission. Intent(Intent.ACTION_INSERT).apply { type = ContactsContract.Contacts.CONTENT_TYPE }.also { intent -> // Make sure that the user has a contacts app installed on their device. intent.resolveActivity(packageManager)?.run { startActivity(intent) } }
Java
// Delegates the responsibility of creating the contact to a contacts app, // which has already been granted the appropriate WRITE_CONTACTS permission. Intent insertContactIntent = new Intent(Intent.ACTION_INSERT); insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE); // Make sure that the user has a contacts app installed on their device. if (insertContactIntent.resolveActivity(getPackageManager()) != null) { startActivity(insertContactIntent); }
Jeśli aplikacja musi wykonywać operacje wejścia/wyjścia na plikach, np. uzyskiwać dostęp do pamięci lub wybierać plik, nie potrzebuje specjalnych uprawnień, ponieważ system może wykonywać te operacje w jej imieniu. Co więcej, gdy użytkownik wybierze treść pod określonym identyfikatorem URI, aplikacja wywołująca otrzyma uprawnienia do wybranego zasobu.
Powiązane informacje:
Bezpieczne udostępnianie danych w aplikacjach
Aby bezpieczniej udostępniać treści aplikacji innym aplikacjom, postępuj zgodnie z tymi sprawdzonymi metodami:
- W razie potrzeby wymuś uprawnienia tylko do odczytu lub tylko do zapisu.
-
Przyznaj klientom jednorazowy dostęp do danych za pomocą flag
FLAG_GRANT_READ_URI_PERMISSION
iFLAG_GRANT_WRITE_URI_PERMISSION
. - Podczas udostępniania danych używaj identyfikatorów URI
content://
, a nie identyfikatorów URIfile://
. InstancjeFileProvider
zrobią to za Ciebie.
Ten fragment kodu pokazuje, jak używać flag przyznawania uprawnień dotyczących identyfikatora URI i uprawnień dostawcy treści do wyświetlania pliku PDF aplikacji w osobnej aplikacji do przeglądania plików PDF:
Kotlin
// Create an Intent to launch a PDF viewer for a file owned by this app. Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("content://com.example/personal-info.pdf") // This flag gives the started app read access to the file. addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }.also { intent -> // Make sure that the user has a PDF viewer app installed on their device. intent.resolveActivity(packageManager)?.run { startActivity(intent) } }
Java
// Create an Intent to launch a PDF viewer for a file owned by this app. Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW); viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf")); // This flag gives the started app read access to the file. viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Make sure that the user has a PDF viewer app installed on their device. if (viewPdfIntent.resolveActivity(getPackageManager()) != null) { startActivity(viewPdfIntent); }
Uwaga: wykonywanie plików z katalogu głównego aplikacji z możliwością zapisu jest naruszeniem zasady W^X.
Z tego powodu niezaufane aplikacje kierowane na Androida 10 (poziom interfejsu API 29) i nowsze nie mogą wywoływać funkcji exec()
w przypadku plików w katalogu domowym aplikacji, a jedynie w przypadku kodu binarnego osadzonego w pliku APK aplikacji.
Aplikacje kierowane na Androida 10 i nowsze nie mogą też modyfikować w pamięci kodu wykonywalnego z plików otwartych za pomocą funkcji dlopen()
. Obejmuje to wszystkie pliki obiektów współdzielonych (.so
) z relokacjami tekstu.
Powiązane informacje:
android:grantUriPermissions
Bezpieczne przechowywanie danych
Aplikacja może wymagać dostępu do poufnych informacji o użytkownikach, ale użytkownicy przyznają jej dostęp do swoich danych tylko wtedy, gdy mają pewność, że odpowiednio je zabezpieczasz.
przechowywać dane prywatne w pamięci wewnętrznej,
Wszystkie prywatne dane użytkownika przechowuj w pamięci wewnętrznej urządzenia, która jest odseparowana od innych aplikacji. Aplikacja nie musi prosić o uprawnienia do wyświetlania tych plików, a inne aplikacje nie mają do nich dostępu. Dodatkowo, gdy użytkownik odinstaluje aplikację, urządzenie usunie wszystkie pliki, które aplikacja zapisała w pamięci wewnętrznej.
Poniższy fragment kodu pokazuje jeden ze sposobów zapisywania danych w pamięci wewnętrznej:
Kotlin
// Creates a file with this name, or replaces an existing file // that has the same name. Note that the file name cannot contain // path separators. val FILE_NAME = "sensitive_info.txt" val fileContents = "This is some top-secret information!" File(filesDir, FILE_NAME).bufferedWriter().use { writer -> writer.write(fileContents) }
Java
// Creates a file with this name, or replaces an existing file // that has the same name. Note that the file name cannot contain // path separators. final String FILE_NAME = "sensitive_info.txt"; String fileContents = "This is some top-secret information!"; try (BufferedWriter writer = new BufferedWriter(new FileWriter(new File(getFilesDir(), FILE_NAME)))) { writer.write(fileContents); } catch (IOException e) { // Handle exception. }
Poniższy fragment kodu pokazuje operację odwrotną, czyli odczytywanie danych z pamięci wewnętrznej:
Kotlin
val FILE_NAME = "sensitive_info.txt" val contents = File(filesDir, FILE_NAME).bufferedReader().useLines { lines -> lines.fold("") { working, line -> "$working\n$line" } }
Java
final String FILE_NAME = "sensitive_info.txt"; StringBuffer stringBuffer = new StringBuffer(); try (BufferedReader reader = new BufferedReader(new FileReader(new File(getFilesDir(), FILE_NAME)))) { String line = reader.readLine(); while (line != null) { stringBuffer.append(line).append('\n'); line = reader.readLine(); } } catch (IOException e) { // Handle exception. }
Powiązane informacje:
Przechowywanie danych w pamięci zewnętrznej w zależności od przypadku użycia
Używaj pamięci zewnętrznej w przypadku dużych, niepoufnych plików, które są specyficzne dla Twojej aplikacji, oraz plików, które aplikacja udostępnia innym aplikacjom. Konkretne interfejsy API, z których korzystasz, zależą od tego, czy aplikacja ma dostęp do plików specyficznych dla aplikacji, czy do plików udostępnionych.
Jeśli plik nie zawiera informacji prywatnych ani poufnych, ale jest przydatny dla użytkownika tylko w Twojej aplikacji, przechowuj go w katalogu aplikacji na pamięci zewnętrznej.
Jeśli aplikacja musi uzyskać dostęp do pliku, który jest przydatny dla innych aplikacji, lub go przechowywać, użyj jednego z tych interfejsów API w zależności od przypadku użycia:
- Pliki multimedialne: aby przechowywać obrazy, pliki audio i filmy udostępniane między aplikacjami oraz uzyskiwać do nich dostęp, używaj interfejsu Media Store API.
- Inne pliki: aby przechowywać inne typy udostępnionych plików, w tym pobrane pliki, i uzyskiwać do nich dostęp, użyj interfejsu Storage Access Framework.
Sprawdzanie dostępności woluminu pamięci
Jeśli Twoja aplikacja wchodzi w interakcję z wymiennym zewnętrznym urządzeniem pamięci masowej, pamiętaj, że użytkownik może usunąć to urządzenie, gdy aplikacja będzie próbować uzyskać do niego dostęp. Dodaj logikę, która sprawdzi, czy urządzenie pamięci masowej jest dostępne.
Sprawdzanie poprawności danych
Jeśli aplikacja korzysta z danych z pamięci zewnętrznej, upewnij się, że zawartość danych nie została uszkodzona ani zmodyfikowana. Uwzględnij logikę obsługi plików, które nie są już w stabilnym formacie.
Poniższy fragment kodu zawiera przykład weryfikatora skrótu:
Kotlin
val hash = calculateHash(stream) // Store "expectedHash" in a secure location. if (hash == expectedHash) { // Work with the content. } // Calculating the hash code can take quite a bit of time, so it shouldn't // be done on the main thread. suspend fun calculateHash(stream: InputStream): String { return withContext(Dispatchers.IO) { val digest = MessageDigest.getInstance("SHA-512") val digestStream = DigestInputStream(stream, digest) while (digestStream.read() != -1) { // The DigestInputStream does the work; nothing for us to do. } digest.digest().joinToString(":") { "%02x".format(it) } } }
Java
Executor threadPoolExecutor = Executors.newFixedThreadPool(4); private interface HashCallback { void onHashCalculated(@Nullable String hash); } boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> { if (Objects.equals(hash, expectedHash)) { // Work with the content. } }); if (!hashRunning) { // There was an error setting up the hash function. } private boolean calculateHash(@NonNull InputStream stream, @NonNull Executor executor, @NonNull HashCallback hashCallback) { final MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-512"); } catch (NoSuchAlgorithmException nsa) { return false; } // Calculating the hash code can take quite a bit of time, so it shouldn't // be done on the main thread. executor.execute(() -> { String hash; try (DigestInputStream digestStream = new DigestInputStream(stream, digest)) { while (digestStream.read() != -1) { // The DigestInputStream does the work; nothing for us to do. } StringBuilder builder = new StringBuilder(); for (byte aByte : digest.digest()) { builder.append(String.format("%02x", aByte)).append(':'); } hash = builder.substring(0, builder.length() - 1); } catch (IOException e) { hash = null; } final String calculatedHash = hash; runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash)); }); return true; }
W plikach pamięci podręcznej przechowuj tylko dane niewrażliwe
Aby zapewnić szybszy dostęp do niepoufnych danych aplikacji, przechowuj je w pamięci podręcznej urządzenia. W przypadku pamięci podręcznych większych niż 1 MB użyj
getExternalCacheDir()
.
W przypadku pamięci podręcznych o rozmiarze 1 MB lub mniejszym użyj
getCacheDir()
.
Obie metody zwracają obiekt File
, który zawiera dane aplikacji zapisane w pamięci podręcznej.
Ten fragment kodu pokazuje, jak zapisać w pamięci podręcznej plik, który aplikacja niedawno pobrała:
Kotlin
val cacheFile = File(myDownloadedFileUri).let { fileToCache -> File(cacheDir.path, fileToCache.name) }
Java
File cacheDir = getCacheDir(); File fileToCache = new File(myDownloadedFileUri); String fileToCacheName = fileToCache.getName(); File cacheFile = new File(cacheDir.getPath(), fileToCacheName);
Uwaga: jeśli używasz
getExternalCacheDir()
do
umieszczania pamięci podręcznej aplikacji w pamięci współdzielonej, użytkownik może wyjąć nośnik
zawierający tę pamięć podczas działania aplikacji. Dodaj logikę, która będzie prawidłowo obsługiwać brak w pamięci podręcznej spowodowany przez to zachowanie użytkownika.
Uwaga: te pliki nie są zabezpieczone.
Dlatego każda aplikacja, która jest kierowana na Androida 10 (API na poziomie 29) lub starszego i ma uprawnienie
WRITE_EXTERNAL_STORAGE
, może uzyskać dostęp do zawartości tej pamięci podręcznej.
Powiązane informacje: Omówienie przechowywania danych i plików
Używanie SharedPreferences w trybie prywatnym
Podczas korzystania z usługi getSharedPreferences()
w celu tworzenia obiektów SharedPreferences
aplikacji lub uzyskiwania do nich dostępu używaj MODE_PRIVATE
. Dzięki temu tylko Twoja aplikacja będzie mieć dostęp do informacji w pliku ustawień współdzielonych.
Jeśli chcesz udostępniać dane w różnych aplikacjach, nie używaj obiektów SharedPreferences
. Zamiast tego wykonaj czynności opisane w artykule Bezpieczne udostępnianie danych w aplikacjach.
Powiązane informacje:
Aktualizowanie usług i zależności
Większość aplikacji korzysta z zewnętrznych bibliotek i informacji o systemie urządzenia, aby wykonywać specjalistyczne zadania. Aktualizując zależności aplikacji, zwiększasz bezpieczeństwo tych punktów komunikacji.
Sprawdzanie dostawcy zabezpieczeń Usług Google Play
Uwaga: ta sekcja dotyczy tylko aplikacji kierowanych na urządzenia, na których są zainstalowane usługi Google Play.
Jeśli Twoja aplikacja korzysta z Usług Google Play, sprawdź, czy są one zaktualizowane na urządzeniu, na którym jest zainstalowana aplikacja. Przeprowadź sprawdzanie asynchronicznie, poza wątkiem interfejsu. Jeśli urządzenie nie jest aktualne, wywołaj błąd autoryzacji.
Aby sprawdzić, czy Usługi Google Play na urządzeniu, na którym jest zainstalowana Twoja aplikacja, są aktualne, wykonaj czynności opisane w przewodniku Aktualizowanie dostawcy zabezpieczeń w celu ochrony przed wykorzystywaniem luk w protokole SSL.
Powiązane informacje:
Aktualizowanie wszystkich zależności aplikacji
Zanim wdrożysz aplikację, upewnij się, że wszystkie biblioteki, pakiety SDK i inne zależności są aktualne:
- W przypadku zależności własnych, takich jak pakiet Android SDK, używaj narzędzi do aktualizacji dostępnych w Android Studio, np. Menedżera SDK.
- W przypadku zależności zewnętrznych sprawdź witryny bibliotek, z których korzysta Twoja aplikacja, i zainstaluj wszystkie dostępne aktualizacje i poprawki zabezpieczeń.
Powiązane informacje: Dodawanie zależności kompilacji
Więcej informacji
Więcej informacji o tym, jak zwiększyć bezpieczeństwo aplikacji, znajdziesz w tych materiałach:
- Lista kontrolna zabezpieczeń dotycząca ogólnej jakości aplikacji
- Program ulepszania zabezpieczeń aplikacji
- Kanał Android Developers w YouTube
- Android Potwierdzenie chronione: nowy poziom bezpieczeństwa transakcji