ข้อมูลที่ละเอียดอ่อนซึ่งจัดเก็บไว้ในที่จัดเก็บข้อมูลภายนอก
จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน
บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ
หมวดหมู่ OWASP: MASVS-STORAGE: พื้นที่เก็บข้อมูล
ภาพรวม
แอปพลิเคชันที่กําหนดเป้าหมายเป็น Android 10 (API ระดับ 29) หรือต่ำกว่าจะไม่บังคับใช้ scoped
storage ซึ่งหมายความว่าข้อมูลทั้งหมดที่เก็บไว้ในที่จัดเก็บข้อมูลภายนอกสามารถ
เข้าถึงโดยแอปพลิเคชันอื่นที่มี 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 ขึ้นไป ระบบปฏิบัติการจะบังคับใช้การใช้พื้นที่เก็บข้อมูลแบบจำกัด กล่าวคือ ระบบจะไม่สนใจ Flag requestLegacyExternalStorage
และปกป้องพื้นที่เก็บข้อมูลภายนอกของแอปพลิเคชันจากการเข้าถึงที่ไม่พึงประสงค์โดยอัตโนมัติ
ใช้พื้นที่เก็บข้อมูลภายในสำหรับข้อมูลที่ละเอียดอ่อน
ไม่ว่า Android เวอร์ชันเป้าหมายจะเป็นเวอร์ชันใด ข้อมูลที่ละเอียดอ่อนของแอปพลิเคชันควรจัดเก็บไว้ในที่จัดเก็บข้อมูลภายในเสมอ การเข้าถึงที่จัดเก็บข้อมูลภายในปัจจุบันคือ
จำกัดไว้เฉพาะแอปพลิเคชันที่เป็นเจ้าของโดยอัตโนมัติด้วยแซนด์บ็อกซ์ของ Android
ดังนั้นจึงถือว่าอุปกรณ์ปลอดภัย เว้นแต่อุปกรณ์จะได้รับการรูท
เข้ารหัสข้อมูลที่ละเอียดอ่อน
หาก Use Case ของแอปพลิเคชันกำหนดให้จัดเก็บข้อมูลที่ละเอียดอ่อนไว้ในพื้นที่เก็บข้อมูลภายนอก ข้อมูลดังกล่าวควรได้รับการเข้ารหัส เราขอแนะนำให้ใช้อัลกอริทึมการเข้ารหัสที่รัดกุมโดยใช้ 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 UTC
[[["เข้าใจง่าย","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 UTC"],[],[],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)"]]