Executive Summary
In the world of mobile apps, keeping user data safe is the top priority. Insecure data storage is a serious problem that happens when apps save sensitive information such as passwords, personal details, or financial data on a device without proper protection. Developers often assume that Android's built-in security is enough, but this is a dangerous mistake.
This white paper explains the risks of insecure data storage, shows how attackers find and exploit these weaknesses, and provides a clear, practical guide to testing for and fixing these problems. We’ll look at real-world examples, such as the breaches in the Tea app and the Baidu SDK, to understand the huge financial, legal, and personal harm that can result from a data leak. By the end of this guide, you will have a clear understanding of why a "secure-by-design" approach is essential and learn how to use modern Android tools to protect your users' information.
1. What Is Insecure Data Storage?
1.1. The Basics of Unprotected Data
In simple terms, insecure data storage means saving private information in a way that is easy for others to access. This could be anything from your username and password to your location data or credit card number. The problem starts when developers think that the device's file system is a secure place. It's not. If an attacker gets access to the device's file system, they can easily read this data if it's not encrypted. This can lead to your accounts being stolen, money being moved from your accounts without permission, or other apps being infected with malware. Insecure storage isn't just a simple mistake; it's a failure to think like a security expert and prioritize user protection.
1.2. Android's Storage Options
Android gives developers a few different places to store data on a device:
Internal storage: This is the most secure option. It's a private "sandbox" for your app's data. Files here can only be seen by your app and are automatically deleted if the app is uninstalled. It's a reliable place for sensitive data, but its security is not absolute if the device is compromised.
External storage: This is for public files, like media or documents. Historically, these files were easy for other apps to read and write. While new versions of Android have improved this with "scoped storage," which limits an app's access, it's still not a safe place for sensitive information without encryption.
Sharedpreferences: This is a simple way to store small bits of data, like user settings. By default, this data is saved in a file as unencrypted text, which is a major security risk.
Databases: For larger, more organized data, apps use a database like SQLite. By default, the entire database is stored without any encryption.
2. How Attackers Find and Exploit Vulnerabilities
2.1. The Attacker's Mindset
Attackers have a simple goal: steal valuable data. They can be anyone from a regular criminal looking to make money to a sophisticated group engaging in corporate espionage. They use a variety of tricks, including social engineering (tricking users into revealing information), to get access to a device and its data.
2.2. The Rooted Device: The Biggest Threat
A rooted Android device is a device that has been modified to give the user "superuser" or "admin" control. This completely breaks Android's main security feature — the app sandbox — which keeps each app's data separate from others. On a rooted device, an attacker can get full access to the device's file system. This means that even if a developer uses internal storage, which is normally private, the attacker can still read all the plaintext data stored there, including in shared_prefs and local databases. This is why developers must always assume the device might be rooted and use an additional layer of security.
2.3. Tools of the Trade: ADB and Decompilers
Attackers use special tools to find and exploit weaknesses:
Decompilers: Tools like jadx or apktool can take an app's code and convert it back into a readable format. This helps attackers find out where an app stores its data and whether it's using any hardcoded passwords.
Android Debug Bridge (ADB): This is a powerful command-line tool that lets a person connect to and control an Android device. On a rooted device, an attacker can use ADB shell to get a command prompt and directly browse the device's file system to copy and read sensitive files.
3. The Consequences: Why It Matters
3.1. Impact on Companies and Users
A data breach can be devastating. For a company, it can lead to huge fines and expensive lawsuits. It also causes severe reputational damage, as customers lose trust and switch to competitors. For users, the consequences are even worse. Leaked information, such as names, phone numbers, and birth dates, can be sold on the dark web and used for long-term identity theft and fraud. Once data is out there, it's out forever, and the user is left to deal with the fallout for years.
3.2. Real-World Examples
The Tea app data leak: A dating safety app called Tea had a massive data leak because its cloud storage was configured incorrectly. The company left a Firebase storage bucket publicly accessible, which allowed anyone with a link to download a database with millions of private messages and photos, including government IDs.
The Baidu SDK leak: Researchers found that popular Android apps, including those from Baidu, were leaking sensitive user and device information to outside servers. The problem was not in Baidu's own code, but in the third-party SDKs it used. This shows that an app's security is only as strong as its weakest link.
4. How to Test for Insecure Data Storage
Finding insecure data storage requires you to think and act like an attacker. Here are the steps and commands to follow for a simple security test.
Step 1: Get the app's package name
First, you need the app's package name. The package name is a unique identifier for the app, for example, com.example.myapp.
Connect your device to your computer via USB with USB debugging enabled.
Open a command prompt or terminal.
Run the ADB command to list all third-party apps and find the one you're testing:
ADB shell pm list packages -3
Step 2: Find the app's data directories
Once you have the package name, you can find where the app's data is stored. On a rooted device, this is usually at /data/data/your.package.name/.
Use the ADB shell command to get a command-line interface to the device.
ADB shellNavigate to the app's data directory. Replace [package_name] with the app's package name.
cd /data/data/[package_name]List the contents of the directory to see if there are any databases or shared_prefs folders.
ls
Step 3: Look for plaintext data
Now, check each of the common insecure storage locations for unencrypted data.
For SharedPreferences: The shared_prefs folder contains XML files. Look inside them for sensitive information.
cd shared_prefs
ls
Then, view the contents of a file, for example:
cat your_preference_file.xml
For databases: The databases folder contains SQLite database files. You can pull this file to your computer and inspect it.
cd databases
ls
Then, pull the file from the device to your computer.
ADB pull /data/data/[package_name]/databases/your_database_file.db
Use a SQLite browser on your computer to open the file and see if the data is in plain text.For external storage: Check the /mnt/sdcard/ directory (or other external storage paths) for any app-related files.
cd /mnt/sdcard/
Remember that even if the files are "hidden" with a . prefix, they are not secure.
Step 4: Use ADB backup (for non-rooted devices)
If the device is not rooted, you can still test for some issues by using the ADB backup command to get a copy of the app's internal memory. This method can be complex and requires additional tools to extract the backup file.
Use ADB backup to back up a specific app:
ADB backup -f your_app_backup.ab [package_name]
5. How to Fix and Secure Your App
The only way to truly protect user data is to assume the device is not secure and build security directly into your app. This is called a "secure-by-design" approach.
5.1. Foundational Principles
Least privilege: Your app should only ask for the permissions it absolutely needs to work. The less access an app has to sensitive information, the less it can accidentally leak.
Secure by default: Don't rely on Android's default settings. Always use encryption for any sensitive data you store.
Never hardcode keys: Do not store encryption keys or passwords directly in your code, as they can easily be found by an attacker who decompiles your app.
5.2. Practical Guide to Secure Storage
For small data (key-value pairs): Instead of standard SharedPreferences, use EncryptedSharedPreferences. It automatically encrypts the data before it's written to a file, making it unreadable if an attacker accesses the file.
How to implement: Add the androidx.security:security-crypto library to your app's build.gradle file.
Secure the key: EncryptedSharedPreferences works by getting a secure master key from the Android Keystore system.
For databases (structured data): Do not use a plaintext SQLite database. Instead, use a library like SQLCipher, which is a secure, encrypted version of SQLite.
How to implement: Add the net.zetetic:sqlcipher-android dependency to your build.gradle file. When creating the database, you will use a SupportFactory to provide an encryption key.
Secure the key: Just like with EncryptedSharedPreferences, the encryption key for SQLCipher should not be hardcoded and should instead be securely generated and stored using the Android Keystore.
For files: If you must save a sensitive file to external storage, it must be encrypted first. Use the EncryptedFile class from the androidx.security library.
Use the Android Keystore system: This is your most important tool for security. It's a special, secure container for cryptographic keys that keeps them separate from your app's main code. Keys stored here cannot be extracted from the device, which makes it nearly impossible for an attacker to steal the key and decrypt your data.
Table 3: Secure Storage Best Practices
Data Type | Insecure Method | Secure Method | Rationale |
Passwords | Store in SharedPreferences or database | Don't store them at all. Use authentication tokens. | Passwords should never be stored on the device. Credential Manager enables passwordless authentication. |
Authentication tokens and API keys | Store in plaintext XML or in code | EncryptedSharedPreferences | Encrypts small key-value pairs automatically, protecting them from attackers. |
User data (PII, etc.) | Unencrypted SQLite database | Encrypted database with SQLCipher | Encrypts the entire database file, preventing attackers from reading its contents. |
Sensitive files | Store on external storage | EncryptedFile on external storage | External storage is not private. Encryption is required to protect files saved there. |
6. Conclusion: A New Mindset
Android's security is constantly improving, but it's not perfect. The only way to keep data truly safe is for developers to change their mindset. Instead of hoping for the best, you must assume the worst and protect your data with multiple layers of security.
By encrypting all sensitive data, securely managing encryption keys with the Android Keystore system, and using modern APIs like EncryptedSharedPreferences, developers can create applications that earn user trust and stand up to modern security threats. Security is an ongoing process, and continuous auditing is a key part of that process.