Các phương pháp hay nhất để bảo mật ứng dụng

Stay organized with collections Save and categorize content based on your preferences.

Việc tăng cường tính bảo mật cho ứng dụng sẽ giúp bạn duy trì sự tin tưởng của người dùng và tính toàn vẹn của thiết bị.

Trang này trình bày một số phương pháp hay nhất có tác động tích cực đáng kể đến tính bảo mật của ứng dụng.

Thực thi hoạt động giao tiếp an toàn

Khi bạn bảo vệ dữ liệu được trao đổi giữa ứng dụng của bạn và các ứng dụng khác hoặc giữa ứng dụng và trang web, bạn sẽ cải thiện độ ổn định của ứng dụng cũng như bảo vệ dữ liệu mà bạn gửi và nhận.

Sử dụng các ý định ngầm ẩn và nhà cung cấp nội dung không được xuất

Hiển thị trình chọn ứng dụng

Nếu một ý định ngầm ẩn có thể mở ít nhất hai ứng dụng trên thiết bị của người dùng, hãy hiển thị rõ ràng trình chọn ứng dụng. Chiến lược tương tác này cho phép người dùng chuyển thông tin nhạy cảm đến một ứng dụng mà họ tin tưởng.

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);
}

Thông tin liên quan:

Áp dụng các quyền dựa trên chữ ký

Khi bạn chia sẻ dữ liệu giữa hai ứng dụng mà bạn kiểm soát hoặc sở hữu, hãy sử dụng các quyền dựa trên chữ ký. Các quyền này không yêu cầu người dùng xác nhận và thay vào đó, hãy kiểm tra để đảm bảo rằng các ứng dụng truy cập vào dữ liệu đã được xác nhận bằng cùng một khoá ký. Do đó, các quyền này đem đến trải nghiệm người dùng an toàn và đơn giản hơn.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <permission android:name="my_custom_permission_name"
                android:protectionLevel="signature" />

Thông tin liên quan:

Không cho phép truy cập thông tin về các nhà cung cấp nội dung của ứng dụng

Trừ khi bạn có ý định gửi dữ liệu từ ứng dụng của mình đến một ứng dụng khác mà bạn không sở hữu, hãy không cho phép ứng dụng của các nhà phát triển khác truy cập vào các đối tượng ContentProvider nằm trong ứng dụng của bạn một cách rõ ràng. Lựa chọn cài đặt này rất quan trọng nếu ứng dụng của bạn có thể được cài đặt trên các thiết bị chạy Android 4.1.1 (API cấp 16) trở xuống, vì theo mặc định, thuộc tính android:exported của phần tử <provider>true trên các phiên bản 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>

Yêu cầu thông tin xác thực trước khi hiện thông tin nhạy cảm

Khi bạn yêu cầu người dùng cung cấp thông tin đăng nhập để họ có thể truy cập vào thông tin nhạy cảm hoặc nội dung cao cấp trong ứng dụng, hãy yêu cầu mã PIN/mật khẩu/hình mở khoá hoặc thông tin xác thực sinh trắc học, chẳng hạn như dùng tính năng nhận dạng khuôn mặt hoặc nhận dạng vân tay.

Để tìm hiểu thêm về cách yêu cầu thông tin xác thực sinh trắc học, hãy xem hướng dẫn về quy trình xác thực sinh trắc học.

Áp dụng các biện pháp bảo mật mạng

Các phần sau đây mô tả cách bạn có thể cải thiện tính bảo mật mạng của ứng dụng.

Sử dụng lưu lượng truy cập SSL

Nếu ứng dụng của bạn giao tiếp với một máy chủ web có chứng chỉ do một tổ chức phát hành chứng chỉ (CA) nổi tiếng và đáng tin cậy cấp, thì yêu cầu HTTPS sẽ rất đơn giản:

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();

Thêm một cấu hình bảo mật mạng

Nếu ứng dụng của bạn dùng CA mới hoặc tuỳ chỉnh, thì bạn có thể khai báo chế độ cài đặt bảo mật mạng trong tệp cấu hình. Quá trình này cho phép bạn tạo cấu hình mà không cần sửa đổi bất kỳ mã ứng dụng nào.

Để thêm một tệp cấu hình bảo mật mạng vào ứng dụng, hãy làm theo các bước sau:

  1. Khai báo cấu hình trong tệp kê khai của ứng dụng:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
    
  3. Thêm một tệp tài nguyên XML nằm tại res/xml/network_security_config.xml.

    Hãy chỉ định rằng tất cả lưu lượng truy cập đến các miền cụ thể đều phải sử dụng HTTPS bằng cách vô hiệu hoá văn bản thô:

    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">secure.example.com</domain>
            ...
        </domain-config>
    </network-security-config>
    

    Trong quá trình phát triển, bạn có thể sử dụng phần tử <debug-overrides> để cho phép rõ ràng các chứng chỉ do người dùng cài đặt. Phần tử này ghi đè các tuỳ chọn quan trọng về bảo mật của ứng dụng trong quá trình gỡ lỗi và kiểm thử mà không ảnh hưởng đến cấu hình phát hành của ứng dụng. Đoạn mã sau đây cho biết cách xác định phần tử này trong tệp XML cấu hình bảo mật mạng của ứng dụng:

    <network-security-config>
        <debug-overrides>
            <trust-anchors>
                <certificates src="user" />
            </trust-anchors>
        </debug-overrides>
    </network-security-config>
    

Thông tin liên quan: Cấu hình bảo mật mạng

Tạo trình quản lý tin cậy của riêng bạn

Trình kiểm tra SSL của bạn không chấp nhận mọi chứng chỉ. Có thể bạn sẽ phải thiết lập trình quản lý tin cậy và xử lý mọi cảnh báo SSL xảy ra nếu trường hợp sử dụng của bạn tuân theo một trong các điều kiện sau:

  • Bạn đang kết nối với một máy chủ web có chứng chỉ do một CA mới hoặc tuỳ chỉnh ký.
  • CA đó không đáng tin cậy đối với thiết bị bạn đang dùng.
  • Bạn không thể sử dụng cấu hình bảo mật mạng.

Để tìm hiểu thêm về cách hoàn thành các bước này, hãy xem phần thảo luận về cách xử lý tổ chức phát hành chứng nhận không xác định.

Thông tin liên quan:

Cẩn trọng khi sử dụng các đối tượng WebView

Bất cứ khi nào có thể, hãy tải nội dung thuộc danh sách cho phép trong đối tượng WebView. Nói cách khác, các đối tượng WebView trong ứng dụng không nên cho phép người dùng chuyển đến những trang web nằm ngoài tầm kiểm soát của bạn.

Ngoài ra, bạn không bao giờ được bật chế độ hỗ trợ giao diện JavaScript trừ khi bạn hoàn toàn nắm quyền kiểm soát và tin tưởng nội dung trong các đối tượng WebView của ứng dụng.

Sử dụng các kênh thư HTML

Nếu ứng dụng của bạn cần sử dụng chế độ hỗ trợ giao diện JavaScript trên các thiết bị chạy Android 6.0 (API cấp 23) trở lên, hãy sử dụng các kênh thư HTML thay vì việc giao tiếp giữa một trang web và ứng dụng, như bạn có thể thấy trong đoạn mã sau:

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"));

Thông tin liên quan:

Cấp các quyền phù hợp

Ứng dụng của bạn chỉ nên yêu cầu số lượng quyền tối thiểu cần thiết để hoạt động bình thường. Khi có thể, ứng dụng của bạn nên từ bỏ một số quyền sau đây khi không dùng đến.

Sử dụng ý định để trì hoãn các quyền

Bất cứ khi nào có thể, không nên thêm quyền vào ứng dụng của bạn để thực hiện một hành động mà ứng dụng khác có thể hoàn thành. Thay vào đó, hãy dùng ý định để trì hoãn yêu cầu này cho một ứng dụng khác đã có quyền cần thiết.

Ví dụ sau đây cho biết cách sử dụng ý định để hướng người dùng chuyển đến một ứng dụng danh bạ thay vì yêu cầu các quyền READ_CONTACTSWRITE_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);
}

Ngoài ra, nếu ứng dụng của bạn cần thực hiện thao tác I/O dựa trên tệp, chẳng hạn như truy cập vào bộ nhớ hoặc chọn tệp, thì ứng dụng không cần các quyền đặc biệt vì hệ thống có thể hoàn thành những thao tác này thay cho ứng dụng. Tốt hơn hết, sau khi người dùng chọn nội dung tại một URI cụ thể, ứng dụng thực hiện lệnh gọi sẽ được cấp quyền đối với tài nguyên đã chọn.

Thông tin liên quan:

Chia sẻ dữ liệu một cách an toàn trên nhiều ứng dụng

Hãy làm theo những phương pháp hay nhất dưới đây để chia sẻ nội dung ứng dụng của bạn với các ứng dụng khác theo cách an toàn hơn:

  • Thực thi quyền chỉ đọc hoặc quyền chỉ ghi khi cần.
  • Cung cấp cho khách hàng quyền truy cập một lần vào dữ liệu bằng cách sử dụng cờ FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION.
  • Khi chia sẻ dữ liệu, hãy dùng các URI "content://", không phải URI "file://". Các thực thể của FileProvider sẽ giúp bạn thực hiện việc này.

Đoạn mã sau đây cho biết cách sử dụng cờ cấp quyền URI và quyền của nhà cung cấp nội dung để hiển thị tệp PDF của một ứng dụng trong ứng dụng Trình xem PDF riêng:

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);
}

Xin lưu ý: Các ứng dụng không đáng tin cậy nhắm đến Android 10 (API cấp 29) trở lên không thể gọi exec() trên các tệp trong thư mục gốc của ứng dụng. Việc thực thi các tệp trong thư mục gốc của ứng dụng có thể coi là một lỗi vi phạm W^X. Các ứng dụng chỉ nên tải mã nhị phân được nhúng trong tệp APK của ứng dụng. Ngoài ra, những ứng dụng nhắm đến Android 10 trở lên không thể sửa đổi mã thực thi trong bộ nhớ của những tệp đã mở bằng dlopen(). Điều này bao gồm tất cả tệp đối tượng được chia sẻ (.so) có hình thức chuyển vị trí văn bản.

Thông tin liên quan: android:grantUriPermissions

Lưu trữ dữ liệu một cách an toàn

Mặc dù ứng dụng của bạn có thể yêu cầu quyền truy cập vào thông tin nhạy cảm, nhưng người dùng chỉ cấp cho ứng dụng quyền truy cập vào dữ liệu nếu họ tin rằng bạn sẽ bảo vệ thông tin đúng cách.

Lưu trữ dữ liệu riêng tư vào bộ nhớ trong

Bộ nhớ trong của thiết bị được đặt trong hộp cát cho mỗi ứng dụng sẽ lưu trữ tất cả dữ liệu riêng tư của người dùng. Ứng dụng của bạn không cần yêu cầu quyền xem các tệp này và các ứng dụng khác cũng không thể truy cập vào tệp. Khi người dùng gỡ cài đặt một ứng dụng, thiết bị sẽ xoá tất cả tệp mà ứng dụng đã lưu vào bộ nhớ trong như một biện pháp bảo mật bổ sung.

Lưu ý: Nếu dữ liệu mà bạn đang lưu trữ đặc biệt nhạy cảm hoặc riêng tư, hãy cân nhắc làm việc với các đối tượng EncryptedFile có sẵn trong Thư viện bảo mật, thay vì các đối tượng File.

Đoạn mã sau đây minh hoạ một cách để ghi dữ liệu vào bộ nhớ:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name, or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the file name cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name, or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the file name cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

Đoạn mã sau đây cho thấy hoạt động đảo ngược, đọc dữ liệu trong bộ nhớ:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToRead),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

Thông tin liên quan:

Lưu trữ dữ liệu vào bộ nhớ ngoài dựa trên trường hợp sử dụng

Hãy sử dụng bộ nhớ ngoài cho những tệp lớn không nhạy cảm cụ thể với ứng dụng cũng như các tệp mà ứng dụng của bạn chia sẻ với các ứng dụng khác. Các API cụ thể mà bạn dùng phụ thuộc vào việc ứng dụng của bạn được thiết kế để truy cập vào các tệp dành riêng cho ứng dụng hay truy cập vào các tệp được chia sẻ.

Kiểm tra dung lượng lưu trữ có sẵn

Nếu ứng dụng của bạn tương tác với thiết bị lưu trữ bên ngoài có thể tháo rời, hãy lưu ý rằng người dùng có thể tháo thiết bị lưu trữ khi ứng dụng đang cố gắng truy cập. Bao gồm logic để xác minh rằng thiết bị lưu trữ hiện có sẵn.

Truy cập vào các tệp dành riêng cho ứng dụng

Nếu tệp không chứa thông tin riêng tư hoặc nhạy cảm mà chỉ cung cấp giá trị cho người dùng trong ứng dụng, hãy lưu trữ tệp trong một thư mục dành riêng cho ứng dụng trên bộ nhớ ngoài.

Truy cập vào các tệp được chia sẻ

Nếu ứng dụng của bạn cần truy cập hoặc lưu trữ một tệp cung cấp giá trị cho các ứng dụng khác, hãy sử dụng một trong những API sau đây tuỳ theo trường hợp sử dụng của bạn:

  • Tệp nội dung nghe nhìn: Để lưu trữ và truy cập vào tệp hình ảnh, tệp âm thanh và video được chia sẻ giữa các ứng dụng, hãy sử dụng API Media Store.
  • Các tệp khác: Để lưu trữ và truy cập vào các loại tệp được chia sẻ khác, kể cả những tệp đã tải xuống, hãy sử dụng Khung truy cập bộ nhớ.

Kiểm tra tính hợp lệ của dữ liệu

Nếu ứng dụng của bạn sử dụng dữ liệu trong bộ nhớ ngoài, hãy đảm bảo rằng nội dung của dữ liệu không bị hỏng hoặc bị sửa đổi. Ứng dụng của bạn cũng nên bao gồm logic để xử lý các tệp không còn ở định dạng ổn định.

Đoạn mã sau đây cho thấy ví dụ về trình xác minh hàm băm:

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;
}

Chỉ lưu trữ dữ liệu không nhạy cảm trong các tệp bộ nhớ đệm

Để truy cập nhanh hơn vào dữ liệu không nhạy cảm của ứng dụng, hãy lưu dữ liệu đó trong bộ nhớ đệm của thiết bị. Đối với các bộ nhớ đệm có kích thước lớn hơn 1 MB, hãy sử dụng getExternalCacheDir(); nếu không, hãy sử dụng getCacheDir(). Mỗi phương thức sẽ cung cấp cho bạn đối tượng File có chứa dữ liệu đã lưu vào bộ nhớ đệm của ứng dụng.

Đoạn mã sau đây cho biết cách lưu tệp vào bộ nhớ đệm mà ứng dụng của bạn đã tải xuống gần đây:

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);

Lưu ý: Nếu bạn sử dụng getExternalCacheDir() để đặt bộ nhớ đệm của ứng dụng trong bộ nhớ dùng chung, thì người dùng có thể tháo phương tiện chứa bộ nhớ này trong khi ứng dụng của bạn đang chạy. Bạn nên bao gồm logic để xử lý linh hoạt tình trạng thiếu bộ nhớ cache do hành vi này của người dùng gây ra.

Chú ý: Không có chế độ bảo mật nào áp dụng cho các tệp này. Do đó, mọi ứng dụng nhắm đến Android 10 (API cấp 29) trở xuống và có quyền WRITE_EXTERNAL_STORAGE có thể truy cập vào nội dung của bộ nhớ đệm này.

Thông tin liên quan: Lưu tệp bộ nhớ đệm

Sử dụng SharedPreferences ở chế độ riêng tư

Khi sử dụng getSharedPreferences() để tạo hoặc truy cập vào các đối tượng SharedPreferences của ứng dụng, hãy sử dụng MODE_PRIVATE. Bằng cách đó, chỉ có ứng dụng của bạn có thể truy cập thông tin trong tệp về lựa chọn chia sẻ ưu tiên.

Nếu bạn muốn chia sẻ dữ liệu trên nhiều ứng dụng, đừng sử dụng đối tượng SharedPreferences. Thay vào đó, bạn nên làm theo các bước cần thiết để chia sẻ dữ liệu trên nhiều ứng dụng một cách an toàn.

Thông tin liên quan: Sử dụng Lựa chọn chia sẻ ưu tiên

Luôn cập nhật các dịch vụ và phần phụ thuộc

Hầu hết ứng dụng đều sử dụng thư viện bên ngoài và thông tin hệ thống của thiết bị để hoàn thành các nhiệm vụ chuyên biệt. Bằng cách cập nhật các phần phụ thuộc của ứng dụng, bạn sẽ khiến các điểm giao tiếp này trở nên an toàn hơn.

Kiểm tra nhà cung cấp dịch vụ bảo mật của Dịch vụ Google Play

Lưu ý: Phần này chỉ áp dụng cho những ứng dụng nhắm đến các thiết bị đã cài đặt Dịch vụ Google Play.

Nếu ứng dụng của bạn sử dụng Dịch vụ Google Play, hãy đảm bảo dịch vụ được cập nhật trên thiết bị cài đặt ứng dụng. Bước kiểm tra này nên được thực hiện không đồng bộ, ngoài luồng giao diện người dùng. Nếu thiết bị không được cập nhật, thì ứng dụng của bạn sẽ kích hoạt lỗi uỷ quyền.

Để xác định xem Dịch vụ Google Play đã được cập nhật trên thiết bị cài đặt ứng dụng của bạn hay chưa, hãy làm theo những bước trong hướng dẫn về Cập nhật Trình cung cấp dịch vụ bảo mật để chống khai thác SSL.

Thông tin liên quan:

Cập nhật tất cả phần phụ thuộc ứng dụng

Trước khi triển khai ứng dụng, hãy đảm bảo rằng bạn đã cập nhật tất cả các thư viện, SDK và các phần phụ thuộc khác:

  • Đối với các phần phụ thuộc bên thứ nhất (chẳng hạn như SDK Android), hãy sử dụng các công cụ cập nhật trong Android Studio, chẳng hạn như Trình quản lý SDK.
  • Đối với các phần phụ thuộc bên thứ ba, hãy kiểm tra trang web của các thư viện mà ứng dụng của bạn sử dụng, đồng thời cài đặt mọi bản cập nhật và bản vá bảo mật có sẵn.

Thông tin liên quan: Thêm phần phụ thuộc của bản dựng

Thông tin khác

Để tìm hiểu thêm về cách tăng cường bảo mật cho ứng dụng của bạn, hãy xem các tài nguyên sau:

Tài nguyên khác

Để biết thêm thông tin về cách giúp ứng dụng của bạn an toàn hơn, hãy tham khảo các tài nguyên sau.

Lớp học lập trình

Blog