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 aplikacją a innymi aplikacjami lub między 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 intencja ogólna może uruchomić co najmniej 2 aplikacje na urządzeniu użytkownika, wyraźnie 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ń opartych na sygnaturze
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 podpisywania. Dlatego te uprawnienia zapewniają bardziej uproszczone i bezpieczne korzystanie z aplikacji.
<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 aplikacji
Jeśli nie zamierzasz wysyłać danych z aplikacji do innej aplikacji, której nie jesteś właścicielem, wyraźnie zablokuj aplikacjom innych deweloperów dostęp do obiektów ContentProvider aplikacji. To
ustawienie jest szczególnie ważne, jeśli aplikację można zainstalować na urządzeniach
z Androidem 4.1.1 (poziom API 16) lub starszym, ponieważ atrybut
android:exported
elementu
<provider>
jest true domyślnie ustawiony na 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śba o dane logowania przed wyświetleniem informacji poufnych
Gdy prosisz użytkowników o dane logowania, aby mogli uzyskać dostęp do informacji poufnych lub treści premium w aplikacji, poproś o kod PIN, hasło, wzór lub dane biometryczne, takie jak rozpoznawanie twarzy lub odcisk palca.
Więcej informacji o tym, jak prosić o dane biometryczne, znajdziesz w przewodniku dotyczącym uwierzytelniania biometrycznego.
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 WWW, który ma certyfikat wydany przez dobrze znany, zaufany urząd certyfikacji (CA), użyj żądania HTTPS, takiego jak to:
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 bezpieczeństwa sieci
Jeśli aplikacja używa nowych lub niestandardowych instytucji certyfikujących, możesz zadeklarować ustawienia bezpieczeństwa sieci w pliku konfiguracyjnym. Ten proces umożliwia utworzenie konfiguracji bez modyfikowania kodu aplikacji.
Aby dodać do aplikacji plik konfiguracji bezpieczeństwa 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 tworzenia możesz użyć elementu
<debug-overrides>, aby wyraźnie zezwolić na używanie certyfikatów zainstalowanych 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 kodu pokazuje, jak zdefiniować ten element w pliku XML konfiguracji bezpieczeństwa 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 bezpieczeństwa 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ługiwanie wszystkich ostrzeżeń TLS, które występują, jeśli w Twoim przypadku spełniony jest jeden z tych warunków:
- Komunikujesz się z serwerem WWW, który ma certyfikat podpisany przez nowy lub niestandardowy urząd certyfikacji.
- Ta instytucja certyfikująca nie jest zaufana przez używane urządzenie.
- Nie możesz użyć konfiguracji bezpieczeństwa sieci.
Więcej informacji o tym, jak wykonać te czynności, znajdziesz w artykule o obsłudze nieznanego urzędu certyfikacji.
Powiązane informacje:
Ostrożne używanie obiektów WebView
Obiekty WebView w aplikacji nie powinny umożliwiać użytkownikom przechodzenia do witryn, które są poza Twoją kontrolą. Jeśli to możliwe, używaj listy dozwolonych, aby ograniczyć treści wczytywane przez obiekty WebView aplikacji.
Ponadto nigdy nie włączaj
obsługi interfejsu JavaScript
support, chyba że masz pełną kontrolę nad treściami w obiektach
WebView aplikacji i im ufasz.
Używanie kanałów wiadomości HTML
Jeśli aplikacja musi używać obsługi interfejsu JavaScript na urządzeniach z Androidem 6.0 (poziom 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. Jeśli to możliwe, zrezygnuj z uprawnień, gdy aplikacja nie będzie ich już potrzebować.
Używanie intencji do odraczania uprawnień
Jeśli to możliwe, nie dodawaj do aplikacji uprawnień do wykonania działania, które można wykonać w innej aplikacji. Zamiast tego użyj intencji, aby odroczyć żądanie do innej aplikacji, która ma już niezbędne uprawnienia.
Ten przykład pokazuje, jak użyć intencji, aby przekierować użytkowników do aplikacji do obsługi 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); }
Ponadto, jeśli aplikacja musi wykonywać operacje wejścia/wyjścia oparte na plikach, takie jak dostęp do pamięci lub wybieranie pliku, nie potrzebuje specjalnych uprawnień, ponieważ system może wykonywać te operacje w imieniu aplikacji. Co więcej, gdy użytkownik wybierze treści pod określonym identyfikatorem URI, aplikacja wywołująca otrzyma uprawnienia do wybranego zasobu.
Powiązane informacje:
Bezpieczne udostępnianie danych między aplikacjami
Aby bezpieczniej udostępniać treści aplikacji innym aplikacjom, postępuj zgodnie z tymi sprawdzonymi metodami:
- W razie potrzeby wymuszaj uprawnienia tylko do odczytu lub tylko do zapisu.
-
Udostępniaj klientom jednorazowy dostęp do danych za pomocą flag
FLAG_GRANT_READ_URI_PERMISSIONiFLAG_GRANT_WRITE_URI_PERMISSION. - Podczas udostępniania danych używaj identyfikatorów URI
content://, a niefile://. InstancjeFileProviderrobią to za Ciebie.
Ten fragment kodu pokazuje, jak używać flag przyznawania uprawnień do identyfikatora URI i uprawnień dostawcy treści, aby wyświetlić plik PDF aplikacji w osobnej aplikacji do wyświetlania 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 zapisywalnego katalogu głównego aplikacji
jest naruszeniem zasady
W^X.
Z tego powodu niezaufane aplikacje, które są przeznaczone na Androida 10 (poziom API 29) lub nowszego, nie mogą wywoływać funkcji exec() w przypadku plików w katalogu głównym aplikacji, tylko w przypadku kodu binarnego osadzonego w pliku APK aplikacji.
Ponadto aplikacje przeznaczone na Androida 10 lub nowszego nie mogą w pamięci modyfikować kodu wykonywalnego z plików otwartych za pomocą funkcji dlopen(). Dotyczy to wszystkich plików obiektów współdzielonych (.so) z relokacjami tekstu.
Powiązane informacje:
android:grantUriPermissions
Bezpieczne przechowywanie danych
Chociaż aplikacja może wymagać dostępu do informacji poufnych użytkownika, użytkownicy przyznają jej dostęp do swoich danych tylko wtedy, gdy ufają, że odpowiednio je zabezpieczasz.
Przechowywanie danych prywatnych w pamięci wewnętrznej
Wszystkie prywatne dane użytkownika przechowuj w pamięci wewnętrznej urządzenia, która jest izolowana dla każdej aplikacji. Aplikacja nie musi prosić o uprawnienia do wyświetlania tych plików, a inne aplikacje nie mogą uzyskać do nich dostępu. Dodatkowo, gdy użytkownik odinstaluje aplikację, urządzenie usunie wszystkie pliki zapisane przez nią w pamięci wewnętrznej.
Ten 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. }
Ten fragment kodu pokazuje operację odwrotną – 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 do przechowywania 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, których używasz, 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 specyficznym dla aplikacji w 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żyj interfejsu Media Store API.
- Inne pliki: aby przechowywać inne typy plików udostępnionych, w tym pobrane pliki, użyj Storage Access Framework.
Sprawdzanie dostępności woluminu pamięci
Jeśli aplikacja wchodzi w interakcję z wymiennym urządzeniem pamięci zewnętrznej, pamiętaj, że użytkownik może usunąć to urządzenie, gdy aplikacja próbuje uzyskać do niego dostęp. Dodaj logikę, która sprawdzi, czy urządzenie pamięci jest dostępne.
Sprawdzanie poprawności danych
Jeśli aplikacja używa danych z pamięci zewnętrznej, upewnij się, że zawartość danych nie została uszkodzona ani zmodyfikowana. Dodaj logikę obsługi plików, które nie są już w stabilnym formacie.
Ten 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; }
Przechowywanie w plikach pamięci podręcznej tylko danych niepoufnych
Aby zapewnić szybszy dostęp do niepoufnych danych aplikacji, przechowuj je w pamięci podręcznej urządzenia. W przypadku pamięci podręcznych o rozmiarze większym niż 1 MB użyj funkcji getExternalCacheDir().
W przypadku pamięci podręcznych o rozmiarze 1 MB lub mniejszym użyj funkcji getCacheDir().
Obie metody udostępniają obiekt File, który zawiera dane z pamięci podręcznej aplikacji.
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 wysunąć nośnik
zawierający tę pamięć, gdy aplikacja jest uruchomiona. Dodaj logikę, która będzie prawidłowo obsługiwać brak w pamięci podręcznej spowodowany takim zachowaniem użytkownika.
Ostrzeżenie: te pliki nie są zabezpieczone.
Dlatego każda aplikacja przeznaczona na Androida 10 (poziom API 29) lub starszego, która ma uprawnienia 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
Gdy używasz
getSharedPreferences() do
tworzenia obiektów SharedPreferences aplikacji lub uzyskiwania do nich dostępu,
używaj MODE_PRIVATE. Dzięki temu tylko Twoja aplikacja może uzyskać dostęp do informacji w pliku preferencji współdzielonych.
Jeśli chcesz udostępniać dane między aplikacjami, nie używaj obiektów SharedPreferences. Zamiast tego wykonaj czynności opisane w sekcji Bezpieczne udostępnianie danych między aplikacjami.
Powiązane informacje:
Aktualizowanie usług i zależności
Większość aplikacji używa bibliotek zewnętrznych i informacji o systemie urządzenia do wykonywania specjalistycznych zadań. 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 przeznaczonych na urządzenia , na których są zainstalowane Usługi Google Play.
Jeśli aplikacja korzysta z Usług Google Play, upewnij się, że są one zaktualizowane na urządzeniu, na którym jest zainstalowana aplikacja. Sprawdź asynchronicznie, poza wątkiem UI. Jeśli urządzenie nie jest aktualne, wywołaj błąd autoryzacji.
Aby sprawdzić, czy usługi Google Play są aktualne na urządzeniu, na którym jest zainstalowana aplikacja, wykonaj czynności opisane w przewodniku o Aktualizowaniu dostawcy zabezpieczeń w celu ochrony przed atakami 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 Android SDK, użyj narzędzi do aktualizacji dostępnych w Android Studio, np. SDK Manager.
- W przypadku zależności zewnętrznych sprawdź witryny bibliotek używanych przez aplikację i zainstaluj wszystkie dostępne aktualizacje i poprawki zabezpieczeń.
Powiązane informacje: Dodawanie zależności kompilacji
Więcej informacji
Aby dowiedzieć się więcej o tym, jak zwiększyć bezpieczeństwo aplikacji, zapoznaj się z tymi materiałami:
- Lista kontrolna bezpieczeństwa ogólnej jakości aplikacji
- Program ulepszania zabezpieczeń aplikacji
- Kanał Android Developers na YouTube
- Android Protected Confirmation: nowy poziom bezpieczeństwa transakcji