Meningkatkan keamanan aplikasi

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:

  1. Deklarasikan konfigurasi dalam manifes aplikasi Anda:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
    
  3. 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>
    

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:

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:

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: