存储在外部存储空间中的敏感数据
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
OWASP 类别:MASVS-STORAGE:存储
概览
以 Android 10(API 29)或更低版本为目标平台的应用不会强制执行分区存储。这意味着,存储在外部存储中的任何数据都可以
可通过 READ_EXTERNAL_STORAGE
访问任何其他应用
权限。
影响
在以 Android 10(API 29)或更低版本为目标平台的应用中,如果敏感数据存储在外部存储空间中,设备上具有 READ_EXTERNAL_STORAGE 权限的任何应用都可以访问这些数据。这会允许恶意应用静默访问永久或临时存储在外部存储空间中的敏感文件。此外,由于外部
系统上的任何应用程序、
还声明了 WRITE_EXTERNAL_STORAGE 权限可以篡改存储的文件
例如包含恶意数据。如果这些恶意数据加载到应用中,可能会用于欺骗用户,甚至执行代码。
缓解措施
分区存储(Android 10 及更高版本)
Android 10
对于以 Android 10 为目标平台的应用,开发者可以明确选择启用分区存储。为此,可将
将 requestLegacyExternalStorage
标志设置为 false
AndroidManifest.xml
文件。借助分区存储,应用只能访问自己在外部存储设备上创建的文件,或使用 MediaStore API 存储的文件类型(例如音频和视频)。这有助于保护用户隐私和安全。
Android 11 及更高版本
对于以 Android 11 或更高版本为目标平台的应用,操作系统会强制使用分区存储,即会忽略 requestLegacyExternalStorage
标志,并自动保护应用的外部存储空间免遭不必要的访问。
将内部存储空间用于敏感数据
无论目标 Android 版本如何,应用的敏感数据都应始终存储在内部存储空间中。对内部存储空间的访问权限为
得益于 Android 沙盒,
因此,除非设备已启用 root 权限,否则会被视为安全。
加密敏感数据
如果应用的用例要求将敏感数据存储在外部
数据,应进行加密。强加密算法是指
建议使用 Android 密钥库来安全存储密钥。
一般来说,建议对所有敏感数据进行加密,无论这些数据存储在何处。
请务必注意,全盘加密
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 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-08-04。
[null,null,["最后更新时间 (UTC):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)"]]