Dengan meningkatkan keamanan aplikasi, Anda membantu menjaga kepercayaan pengguna dan integritas perangkat.
Halaman ini menyajikan praktik terbaik yang memiliki dampak positif dan signifikan pada keamanan aplikasi.
Menerapkan komunikasi yang aman
Dengan mengamankan data yang dipertukarkan antara aplikasi Anda dengan aplikasi lain, atau antara aplikasi Anda dengan sebuah situs, Anda akan meningkatkan stabilitas aplikasi dan melindungi data yang Anda kirim dan terima.
Mengamankan komunikasi antar-aplikasi
Untuk berkomunikasi antar-aplikasi dengan lebih aman, gunakan intent implisit dengan pemilih aplikasi, izin berbasis tanda tangan, dan penyedia konten yang tidak diekspor.
Menampilkan pemilih aplikasi
Jika sebuah intent implisit dapat meluncurkan minimal dua aplikasi pada perangkat pengguna, maka tampilkan pemilih aplikasi secara eksplisit. Strategi interaksi ini memungkinkan pengguna mentransfer informasi sensitif ke aplikasi yang mereka percayai.
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); }
Info terkait:
Menerapkan izin berbasis tanda tangan
Saat berbagi data antara dua aplikasi yang Anda kontrol atau miliki, gunakan izin berbasis tanda tangan. Izin ini tidak mengharuskan konfirmasi pengguna dan, sebagai gantinya, memeriksa bahwa aplikasi yang mengakses data ditandatangani menggunakan kunci penandatanganan yang sama. Oleh karena itu, izin ini menawarkan pengalaman pengguna yang lebih aman dan sederhana.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <permission android:name="my_custom_permission_name" android:protectionLevel="signature" />
Info terkait:
Melarang akses ke penyedia konten aplikasi
Kecuali jika bermaksud mengirimkan data dari aplikasi Anda ke aplikasi lain yang
bukan milik Anda, secara eksplisit larang aplikasi developer lain mengakses
objek ContentProvider
aplikasi Anda. Setelan
ini sangat penting jika aplikasi Anda dapat diinstal di perangkat yang
menjalankan Android 4.1.1 (level API 16) atau yang lebih rendah, karena atribut
android:exported
elemen
<provider>
secara default adalah true
pada versi Android tersebut.
<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>
Meminta kredensial sebelum menampilkan informasi sensitif
Saat meminta kredensial dari pengguna agar mereka dapat mengakses informasi sensitif atau konten premium dalam aplikasi Anda, mintalah PIN/sandi/pola atau kredensial biometrik, seperti pengenalan wajah atau pengenalan sidik jari.
Untuk mempelajari cara meminta kredensial biometrik lebih lanjut, lihat panduan tentang autentikasi biometrik.
Menerapkan langkah pengamanan jaringan
Bagian berikut menjelaskan cara meningkatkan keamanan jaringan aplikasi.
Menggunakan traffic TLS
Jika aplikasi Anda berkomunikasi dengan server web yang memiliki sertifikat dari certificate authority (CA) terkenal dan tepercaya, gunakan permintaan HTTPS seperti berikut:
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();
Tambahkan konfigurasi keamanan jaringan
Jika aplikasi Anda menggunakan CA baru atau kustom, Anda dapat mendeklarasikan setelan keamanan jaringan dalam file konfigurasi. Proses ini memungkinkan Anda membuat konfigurasi tanpa mengubah kode aplikasi apa pun.
Untuk menambahkan file konfigurasi keamanan jaringan ke aplikasi Anda, ikuti langkah-langkah berikut:
- Deklarasikan konfigurasi dalam manifes aplikasi Anda:
-
Tambahkan file resource XML, yang terletak di
res/xml/network_security_config.xml
.Tentukan bahwa semua traffic ke domain tertentu harus menggunakan HTTPS dengan menonaktifkan clear-text:
<network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">secure.example.com</domain> ... </domain-config> </network-security-config>
Selama proses pengembangan, Anda dapat menggunakan elemen
<debug-overrides>
untuk secara eksplisit mengizinkan sertifikat yang diinstal oleh pengguna. Elemen ini menggantikan opsi yang penting bagi keamanan aplikasi Anda selama proses debug dan pengujian tanpa memengaruhi konfigurasi rilis aplikasi. Cuplikan berikut menunjukkan cara menetapkan elemen ini dalam file XML konfigurasi keamanan jaringan aplikasi Anda:<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>
Info terkait: Konfigurasi keamanan jaringan
Membuat pengelola kepercayaan Anda sendiri
Pemeriksa TLS Anda tidak boleh menerima setiap sertifikat. Anda mungkin perlu menyiapkan pengelola kepercayaan dan menangani semua peringatan TLS yang terjadi jika salah satu dari kondisi berikut terjadi dalam kasus penggunaan Anda:
- Anda berkomunikasi dengan server web yang memiliki sertifikat yang ditandatangani oleh CA baru atau kustom.
- CA itu tidak dipercayai oleh perangkat yang Anda gunakan.
- Anda tidak dapat menggunakan konfigurasi keamanan jaringan.
Untuk mempelajari cara menyelesaikan langkah-langkah ini lebih lanjut, lihat pembahasan tentang menangani certificate authority yang tidak dikenal.
Info terkait:
Menggunakan objek WebView dengan hati-hati
Objek WebView
di aplikasi Anda tidak boleh mengizinkan pengguna membuka situs yang berada di luar
kontrol Anda. Jika memungkinkan, gunakan daftar yang diizinkan untuk membatasi konten yang dimuat
oleh objek WebView
aplikasi Anda.
Selain itu, jangan mengaktifkan
dukungan antarmuka
JavaScript kecuali jika Anda sepenuhnya mengontrol dan memercayai konten dalam objek
WebView
aplikasi.
Menggunakan saluran pesan HTML
Jika aplikasi Anda harus menggunakan dukungan antarmuka JavaScript pada perangkat yang menjalankan Android 6.0 (level API 23) dan yang lebih tinggi, gunakan saluran pesan HTML alih-alih berkomunikasi antara situs web dan aplikasi Anda, seperti yang ditampilkan di cuplikan kode berikut:
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"));
Info terkait:
Memberikan izin yang tepat
Hanya minta izin sebanyak yang diperlukan aplikasi Anda agar dapat berfungsi dengan baik. Jika memungkinkan, batalkan izin saat aplikasi Anda tidak lagi memerlukannya.
Menggunakan intent untuk mengalihkan izin
Jika memungkinkan, jangan tambahkan izin ke aplikasi untuk menyelesaikan tindakan yang dapat diselesaikan di aplikasi lain. Sebagai gantinya, gunakan intent untuk mengalihkan permintaan tersebut ke aplikasi lain yang sudah memiliki izin yang diperlukan.
Contoh berikut menunjukkan cara menggunakan intent untuk mengarahkan pengguna ke
aplikasi kontak, bukan meminta izin
READ_CONTACTS
dan
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); }
Selain itu, jika aplikasi Anda perlu melakukan I/O berbasis file—seperti mengakses penyimpanan atau memilih file—aplikasi tersebut tidak memerlukan izin khusus karena sistem dapat menyelesaikan operasi itu atas nama aplikasi Anda. Lebih baik lagi, setelah pengguna memilih konten di URI tertentu, aplikasi yang melakukan panggilan akan mendapat izin ke resource yang dipilih.
Info terkait:
Berbagi data dengan banyak aplikasi dengan aman
Ikuti praktik terbaik berikut untuk membagikan konten aplikasi Anda kepada aplikasi lain dengan cara yang lebih aman:
- Terapkan izin hanya baca atau hanya tulis sesuai kebutuhan.
-
Beri klien akses satu kali ke data menggunakan flag
FLAG_GRANT_READ_URI_PERMISSION
danFLAG_GRANT_WRITE_URI_PERMISSION
. - Saat berbagi data, gunakan URI
content://
, bukan URIfile://
. InstanceFileProvider
melakukannya untuk Anda.
Cuplikan kode berikut menunjukkan cara menggunakan flag pemberian izin URI dan izin penyedia konten untuk menampilkan file PDF aplikasi dalam aplikasi penampil PDF tersendiri:
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); }
Catatan: Menjalankan file dari direktori utama aplikasi yang dapat ditulis
merupakan
pelanggaran W^X.
Oleh karena itu, aplikasi tidak tepercaya yang menargetkan Android 10 (level API 29) dan yang lebih baru tidak dapat
memanggil exec()
pada file dalam direktori utama aplikasi, hanya
kode biner yang disematkan dalam file APK aplikasi.
Selain itu, aplikasi yang menargetkan Android 10 dan yang lebih baru, dalam memori,
tidak dapat mengubah kode yang dapat dieksekusi dari file yang telah dibuka dengan
dlopen()
. Ini termasuk semua file objek bersama (.so
)
yang berisi relokasi teks.
Info terkait:
android:grantUriPermissions
Menyimpan data dengan aman
Meskipun aplikasi Anda mungkin memerlukan akses ke informasi pengguna yang sensitif, pengguna memberi aplikasi Anda akses ke data mereka hanya jika mereka percaya bahwa Anda mengamankannya dengan benar.
Menyimpan data pribadi dalam penyimpanan internal
Simpan semua data pribadi pengguna dalam penyimpanan internal perangkat, yang di-sandbox untuk setiap aplikasi. Aplikasi Anda tidak perlu meminta izin untuk melihat file ini, dan aplikasi lain tidak dapat mengakses file tersebut. Sebagai tindakan pengamanan tambahan, saat pengguna meng-uninstal aplikasi, perangkat akan menghapus semua file yang disimpan oleh aplikasi tersebut dalam penyimpanan internal.
Cuplikan kode berikut menunjukkan satu cara untuk menulis data ke penyimpanan internal:
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. }
Cuplikan kode berikut menunjukkan operasi terbalik, yang membaca data dari penyimpanan internal:
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. }
Info terkait:
Menyimpan data dalam penyimpanan eksternal berdasarkan kasus penggunaan
Gunakan penyimpanan eksternal untuk file besar yang tidak bersifat sensitif, yang khusus untuk aplikasi Anda, serta file yang dibagikan aplikasi Anda ke aplikasi lain. API khusus yang Anda gunakan bergantung pada apakah aplikasi Anda dirancang untuk mengakses file khusus aplikasi atau mengakses file bersama.
Jika file tidak berisi informasi yang bersifat pribadi atau sensitif tetapi memberi nilai kepada pengguna hanya di aplikasi Anda saja, simpan file di direktori khusus aplikasi di penyimpanan eksternal.
Jika aplikasi Anda perlu mengakses atau menyimpan file yang memberi nilai kepada aplikasi lain, gunakan salah satu API berikut sesuai dengan kasus penggunaan Anda:
- File media: Untuk menyimpan dan mengakses gambar, file audio, dan video yang dibagikan antar-aplikasi, gunakan Media Store API.
- File lainnya: Untuk menyimpan dan mengakses file bersama jenis lain, termasuk file yang didownload, gunakan Storage Access Framework.
Memeriksa ketersediaan volume penyimpanan
Jika aplikasi Anda berinteraksi dengan perangkat penyimpanan eksternal yang dapat dilepas, perlu diingat bahwa pengguna dapat melepas perangkat penyimpanan saat aplikasi Anda mencoba mengaksesnya. Sertakan logika untuk memverifikasi bahwa perangkat penyimpanan tersedia.
Memeriksa validitas data
Jika aplikasi Anda menggunakan data dari penyimpanan eksternal, pastikan isi data belum rusak atau diubah. Sertakan logika untuk menangani file yang tidak lagi dalam format yang stabil.
Cuplikan kode berikut menyertakan contoh pemverifikasi hash:
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; }
Menyimpan hanya data yang tidak sensitif dalam file cache
Untuk memberikan akses lebih cepat ke data aplikasi yang tidak bersifat sensitif, simpan data tersebut dalam
cache perangkat. Untuk cache yang lebih besar dari 1 MB, gunakan
getExternalCacheDir()
.
Untuk cache berukuran 1 MB atau lebih kecil, gunakan
getCacheDir()
.
Kedua metode tersebut memberi Anda
objek File
yang
berisi data yang di-cache aplikasi.
Cuplikan kode berikut menunjukkan cara meng-cache file yang baru saja didownload oleh aplikasi:
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);
Catatan: Jika Anda menggunakan
getExternalCacheDir()
untuk
menempatkan cache aplikasi Anda di dalam penyimpanan bersama, pengguna mungkin mengeluarkan media
yang berisi penyimpanan ini selagi aplikasi Anda berjalan. Sertakan logika untuk
menangani dengan baik cache yang tidak ditemukan yang disebabkan oleh perilaku pengguna ini.
Perhatian: Tidak ada pengamanan yang diberlakukan pada file ini.
Oleh karena itu, aplikasi apa pun yang menargetkan Android 10 (API level 29) atau yang lebih rendah dan memiliki izin
WRITE_EXTERNAL_STORAGE
dapat mengakses
konten cache ini.
Info terkait: Ringkasan penyimpanan data dan file
Menggunakan SharedPreferences dalam mode pribadi
Saat menggunakan
getSharedPreferences()
untuk
membuat atau mengakses objek SharedPreferences
pada aplikasi Anda,
gunakan MODE_PRIVATE
. Dengan begitu, hanya aplikasi Anda yang dapat
mengakses informasi dalam file preferensi bersama ini.
Jika Anda ingin berbagi data dengan aplikasi lain, jangan gunakan
objek
SharedPreferences
. Sebagai gantinya, ikuti langkah-langkah untuk membagikan
data dengan banyak aplikasi dengan aman.
Library Security juga menyediakan class EncryptedSharedPreferences yang menggabungkan class SharedPreferences dan otomatis mengenkripsi kunci dan nilai.
Info terkait:
Menjaga agar layanan dan dependensi tetap terbaru
Sebagian besar aplikasi menggunakan library eksternal dan informasi sistem perangkat untuk menyelesaikan tugas-tugas khusus. Dengan menjaga dependensi aplikasi Anda tetap terbaru, Anda meningkatkan keamanan titik-titik komunikasi ini.
Memeriksa penyedia keamanan layanan Google Play
Catatan: Bagian ini hanya berlaku pada aplikasi yang menargetkan perangkat yang telah menginstal layanan Google Play.
Jika aplikasi Anda menggunakan layanan Google Play, pastikan layanan tersebut diupdate di perangkat yang menginstal aplikasi Anda. Lakukan pemeriksaan asinkron, terlepas dari UI thread. Jika perangkat tidak diupdate, picu error otorisasi.
Untuk menentukan apakah layanan Google Play di perangkat yang menginstal aplikasi Anda sudah merupakan versi terbaru atau bukan, ikuti langkah-langkah dalam panduan untuk Memperbarui penyedia keamanan untuk melindungi dari eksploitasi SSL.
Info terkait:
Memperbarui semua dependensi aplikasi
Sebelum men-deploy aplikasi Anda, pastikan semua library, SDK, dan dependensi lainnya adalah versi terbaru:
- Untuk dependensi pihak pertama, seperti Android SDK, gunakan alat update yang tersedia di Android Studio, seperti SDK Manager.
- Untuk dependensi pihak ketiga, periksa situs library yang digunakan aplikasi Anda, lalu instal semua update dan patch keamanan yang tersedia.
Info terkait: Menambahkan dependensi build
Informasi selengkapnya
Untuk mempelajari cara meningkatkan keamanan aplikasi Anda lebih lanjut, pelajari referensi berikut:
- Checklist keamanan kualitas aplikasi inti
- Program peningkatan keamanan aplikasi
- Channel Android Developers di YouTube
- Codelab Konfigurasi Keamanan Jaringan Android
- Konfirmasi Dilindungi oleh Android: Meningkatkan keamanan transaksi