儲存在外部儲存空間的機密資料
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
OWASP 類別:MASVS-STORAGE:儲存空間
總覽
以 Android 10 (API 29) 以下版本為目標版本的應用程式不會強制執行範圍限制
如果 30 天內讀取資料不到一次
建議使用 Nearline Storage也就是說,任何儲存在外部儲存空間的資料,都可以由具備 READ_EXTERNAL_STORAGE
權限的任何其他應用程式存取。
影響
指定 Android 10 (API 29) 以下版本為目標版本的應用程式中,如果機密資料為
儲存儲存在外部儲存空間的任何應用程式
READ_EXTERNAL_STORAGE 權限可存取此 API。這會造成惡意
隨時視需要永久或暫時存取機密檔案
儲存在外部儲存空間此外,由於系統中的任何應用程式都能存取外部儲存空間中的內容,因此任何宣告 WRITE_EXTERNAL_STORAGE 權限的惡意應用程式,都可能竄改儲存在外部儲存空間中的檔案,例如加入惡意資料。這種惡意行為
載入應用程式中時,也可能用於欺騙使用者,甚至
即可執行程式碼
因應措施
限定範圍儲存空間 (Android 10 以上版本)
Android 10
針對指定 Android 10 為目標版本的應用程式,開發人員可以明確選擇加入受限儲存空間。只需設定
在 requestLegacyExternalStorage
標記設為 false:
AndroidManifest.xml
檔案。有了限定範圍儲存空間,應用程式只能存取在外部儲存空間中自行建立的檔案,或是使用 MediaStore API 儲存的檔案類型,例如音訊和影片。這有助於保護使用者的隱私權和安全。
Android 11 以上版本
針對指定 Android 11 以上版本為目標的應用程式,作業系統會強制執行
限定範圍儲存空間因此系統會忽略
requestLegacyExternalStorage
標記並自動保護
應用程式防止不必要的存取外部儲存空間
使用內部儲存空間存放機密資料
無論指定的 Android 版本為何,應用程式的機密資料
一律應儲存在內部儲存空間中內部儲存空間的存取權為
有了 Android 沙箱機制
因此除非裝置已啟用 Root 權限,否則系統將視為安全裝置。
加密機密資料
如果應用程式的用途需要將機密資料儲存在外部
如果儲存,資料就必須加密。建議您使用強大的加密演算法,並透過 Android KeyStore 安全地儲存金鑰。
一般而言,將所有機密資料加密是建議的安全做法,
無論是儲存在何處
請注意,全磁碟加密 (或 Android 10 的檔案型加密) 是一種措施,可防止他人透過實體存取和其他攻擊途徑存取資料。因此,要授予相同的安全措施
同時,您也必須對儲存在外部儲存空間的資料進行加密,
應用程式。
如果需要將資料從外部儲存空間載入到
完整性檢查功能,確認沒有其他應用程式遭到竄改
包含這項資料或程式碼檔案雜湊應儲存
並儲存在內部儲存空間中
Kotlin
package com.example.myapplication
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object FileIntegrityChecker {
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun getIntegrityHash(filePath: String?): String {
val md = MessageDigest.getInstance("SHA-256") // You can choose other algorithms as needed
val buffer = ByteArray(8192)
var bytesRead: Int
BufferedInputStream(FileInputStream(filePath)).use { fis ->
while (fis.read(buffer).also { bytesRead = it } != -1) {
md.update(buffer, 0, bytesRead)
}
}
private fun bytesToHex(bytes: ByteArray): String {
val sb = StringBuilder()
for (b in bytes) {
sb.append(String.format("%02x", b))
}
return sb.toString()
}
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {
val actualHash = getIntegrityHash(filePath)
return actualHash == expectedHash
}
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val filePath = "/path/to/your/file"
val expectedHash = "your_expected_hash_value"
if (verifyIntegrity(filePath, expectedHash)) {
println("File integrity is valid!")
} else {
println("File integrity is compromised!")
}
}
}
Java
package com.example.myapplication;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityChecker {
public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // You can choose other algorithms as needed
byte[] buffer = new byte[8192];
int bytesRead;
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
}
byte[] digest = md.digest();
return bytesToHex(digest);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {
String actualHash = getIntegrityHash(filePath);
return actualHash.equals(expectedHash);
}
public static void main(String[] args) throws Exception {
String filePath = "/path/to/your/file";
String expectedHash = "your_expected_hash_value";
if (verifyIntegrity(filePath, expectedHash)) {
System.out.println("File integrity is valid!");
} else {
System.out.println("File integrity is compromised!");
}
}
}
資源
這個頁面中的內容和程式碼範例均受《內容授權》中的授權所規範。Java 與 OpenJDK 是 Oracle 和/或其關係企業的商標或註冊商標。
上次更新時間:2025-08-04 (世界標準時間)。
[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-08-04 (世界標準時間)。"],[],[],null,["# Sensitive Data Stored in External Storage\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-STORAGE: Storage](https://mas.owasp.org/MASVS/05-MASVS-STORAGE)\n\nOverview\n--------\n\nApplications targeting Android 10 (API 29) or lower don't enforce [scoped\nstorage](/training/data-storage#scoped-storage). This means that any data stored on the external storage can be\naccessed by any other application with the [`READ_EXTERNAL_STORAGE`](/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE)\npermission.\n\nImpact\n------\n\nIn applications targeting Android 10 (API 29) or lower, if sensitive data is\nstored on the external storage, any application on the device with the\nREAD_EXTERNAL_STORAGE permission can access it. This allows malicious\napplications to silently access sensitive files permanently or temporarily\nstored on the external storage. Additionally, since content on the external\nstorage can be accessed by any app on the system, any malicious application that\nalso declares the WRITE_EXTERNAL_STORAGE permission can tamper with files stored\non the external storage, e.g. to include malicious data. This malicious\ndata, if loaded into the application, could be designed to deceive users or even\nachieve code execution.\n\nMitigations\n-----------\n\n### Scoped Storage (Android 10 and later)\n\n##### Android 10\n\nFor applications targeting Android 10, developers can explicitly opt-in to\nscoped storage. This can be achieved by setting the\n[`requestLegacyExternalStorage`](/reference/android/R.attr#requestLegacyExternalStorage) flag to **false** in the\n`AndroidManifest.xml` file. With scoped storage, applications can only access\nfiles that they have created themselves on the external storage or files types\nthat were stored using the [MediaStore API](/reference/android/provider/MediaStore) such as Audio and Video. This\nhelps protect user privacy and security.\n\n##### Android 11 and later\n\nFor applications targeting Android 11 or later versions, the OS [enforces the\nuse of scoped storage](/about/versions/11/privacy/storage#scoped-storage), i.e. it ignores the\n[`requestLegacyExternalStorage`](/reference/android/R.attr#requestLegacyExternalStorage) flag and automatically protects\napplications' external storage from unwanted access.\n\n### Use Internal Storage for Sensitive Data\n\nRegardless of the targeted Android version, an application's sensitive data\nshould always be stored on internal storage. Access to internal storage is\nautomatically restricted to the owning application thanks to Android sandboxing,\ntherefore it can be considered secure, unless the device is rooted.\n\n### Encrypt sensitive data\n\nIf the application's use cases require storing sensitive data on the external\nstorage, the data should be encrypted. A strong encryption algorithm is\nrecommended, using the [Android KeyStore](/privacy-and-security/keystore) to safely store the key.\n\nIn general, encrypting all sensitive data is a recommended security practice, no\nmatter where it is stored.\n\nIt is important to note that full disk encryption (or file-based encryption from\nAndroid 10) is a measure aimed at protecting data from physical access and other\nattack vectors. Because of this, to grant the same security measure, sensitive\ndata held on external storage should additionally be encrypted by the\napplication.\n\n### Perform integrity checks\n\nIn cases where data or code has to be loaded from the external storage into the\napplication, integrity checks to verify that no other application has tampered\nwith this data or code are recommended. The hashes of the files should be stored\nin a secure manner, preferably encrypted and in the internal storage. \n\n### Kotlin\n\n package com.example.myapplication\n\n import java.io.BufferedInputStream\n import java.io.FileInputStream\n import java.io.IOException\n import java.security.MessageDigest\n import java.security.NoSuchAlgorithmException\n\n object FileIntegrityChecker {\n @Throws(IOException::class, NoSuchAlgorithmException::class)\n fun getIntegrityHash(filePath: String?): String {\n val md = MessageDigest.getInstance(\"SHA-256\") // You can choose other algorithms as needed\n val buffer = ByteArray(8192)\n var bytesRead: Int\n BufferedInputStream(FileInputStream(filePath)).use { fis -\u003e\n while (fis.read(buffer).also { bytesRead = it } != -1) {\n md.update(buffer, 0, bytesRead)\n }\n\n }\n\n private fun bytesToHex(bytes: ByteArray): String {\n val sb = StringBuilder()\n for (b in bytes) {\n sb.append(String.format(\"%02x\", b))\n }\n return sb.toString()\n }\n\n @Throws(IOException::class, NoSuchAlgorithmException::class)\n fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {\n val actualHash = getIntegrityHash(filePath)\n return actualHash == expectedHash\n }\n\n @Throws(Exception::class)\n @JvmStatic\n fun main(args: Array\u003cString\u003e) {\n val filePath = \"/path/to/your/file\"\n val expectedHash = \"your_expected_hash_value\"\n if (verifyIntegrity(filePath, expectedHash)) {\n println(\"File integrity is valid!\")\n } else {\n println(\"File integrity is compromised!\")\n }\n }\n }\n\n### Java\n\n package com.example.myapplication;\n\n import java.io.BufferedInputStream;\n import java.io.FileInputStream;\n import java.io.IOException;\n import java.security.MessageDigest;\n import java.security.NoSuchAlgorithmException;\n\n public class FileIntegrityChecker {\n\n public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); // You can choose other algorithms as needed\n byte[] buffer = new byte[8192];\n int bytesRead;\n\n try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {\n while ((bytesRead = fis.read(buffer)) != -1) {\n md.update(buffer, 0, bytesRead);\n }\n }\n\n byte[] digest = md.digest();\n return bytesToHex(digest);\n }\n\n private static String bytesToHex(byte[] bytes) {\n StringBuilder sb = new StringBuilder();\n for (byte b : bytes) {\n sb.append(String.format(\"%02x\", b));\n }\n return sb.toString();\n }\n\n public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {\n String actualHash = getIntegrityHash(filePath);\n return actualHash.equals(expectedHash);\n }\n\n public static void main(String[] args) throws Exception {\n String filePath = \"/path/to/your/file\";\n String expectedHash = \"your_expected_hash_value\";\n\n if (verifyIntegrity(filePath, expectedHash)) {\n System.out.println(\"File integrity is valid!\");\n } else {\n System.out.println(\"File integrity is compromised!\");\n }\n }\n }\n\nResources\n---------\n\n- [Scoped storage](/training/data-storage#scoped-storage)\n- [READ_EXTERNAL_STORAGE](/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE)\n- [WRITE_EXTERNAL_STORAGE](/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE)\n- [requestLegacyExternalStorage](/reference/android/R.attr#requestLegacyExternalStorage)\n- [Data and file storage overview](/training/data-storage)\n- [Data Storage (App Specific)](/training/data-storage/app-specific)\n- [Cryptography](/privacy-and-security/cryptography)\n- [Keystore](/privacy-and-security/keystore)\n- [File-Based encryption](https://source.android.com/docs/security/features/encryption/file-based)\n- [Full-Disk encryption](https://source.android.com/docs/security/features/encryption/full-disk)"]]