OWASP Top 10 For Flutter – M9: Insecure Data Storage in Flutter & Dart

Welcome back to our deep dive into the OWASP Mobile Top 10 for Flutter developersarrow-up-right.

In earlier parts we tackled:

Each is a critical piece of the mobile security puzzle.

In this ninth article, we focus on M9: Insecure Data Storage.

Let me start with a story that still makes me cringe.

A few years ago, I was doing a security review of a health-tracking app. I discovered that the developers stored complete medical records in a plain JSON file. No encryption. No access controls. Readable by anyone with a few minutes of physical access to the device.

The developers weren’t malicious. They simply didn’t realize that “saving to a file” on mobile isn’t like saving to a server behind a firewall. Mobile devices get lost, stolen, backed up to cloud services, and sometimes compromised by malware. Every piece of data you store becomes a potential liability.

OWASP M9: Insecure Data Storagearrow-up-right addresses exactly this problem. It’s the improper protection of sensitive data at rest. It’s also one of the most common vulnerabilities in mobile apps.

Flutter’s cross-platform nature adds an extra layer of complexity. When you call SharedPreferences.setString(), do you know where that data ends up on Android versus iOS? Do you know who can access it?

Let’s get into it.

book-blank

Understanding the Threat Landscape

Before we get to the solutions, I want to give you a clear picture of what you're actually defending against. The threat isn't abstract. There are real people (and automated tools) that go after insecure storage.

Who's After Your Data?

You might be surprised how many different types of attackers care about what your app stores:

Who
Motivation
Attack Method

Hackers

Data extraction, credential theft

Reverse engineering, file system access

Malicious Insiders

Privilege abuse, data exfiltration

Direct database/file access

State-Sponsored Groups

Espionage, mass surveillance

Advanced persistent threats

Cybercriminals

Financial gain, ransomware

Malware, data selling on dark web

Competitors

Corporate espionage

Supply chain attacks, insider recruitment

Data Brokers

Selling personal information

Automated scraping of insecure apps

You might think your app isn't a big enough target to worry about. But here's the thing: automated tools don't discriminate. A script that scans backup files for stored credentials doesn't care whether your app has 1,000 users or 1,000,000.

How Attackers Get Your Data

The exploitability of insecure data storage is rated EASYarrow-up-right by OWASP. That's the highest exploitability rating, and it's accurate.

  1. Physical device access: even a few minutes with an unlocked device can be enough. Think lost phones, repair shops, or handing your phone over “just to see the photo”.

  2. Rooted/jailbroken devices: sandboxes don’t help. Malware with privileged access can read your app’s private storage.

  3. Backup extraction: a common blind spot. Backups to iCloud or Google may include app data in plain text.

  4. Malware: a malicious app can read other apps' insecure storage on rooted devices. It can also abuse backup mechanisms.

  5. Social engineering: tricking users into installing apps that request broad permissions and harvest stored data.

What Makes Flutter Apps Vulnerable?

The security weaknesses I see most often in Flutter apps fall into predictablepatterns:

Flutter Storage Mechanisms: A Security Deep Dive

Before we get to the solutions, it helps to understand exactly what you'redealing with. Flutter gives you several built-in ways to store data locally, andeach one has a completely different security profile. The bad news? Three of thefour most commonly used options are completely insecure for sensitive data.

1. SharedPreferences / NSUserDefaults

SharedPreferences is the go-to solution for storing simple key-value pairs inFlutter. It's convenient, easy to use, and completely insecure for sensitivedata. I see it misused constantly. developers store auth tokens, API keys, andsometimes even passwords here.

Here's what I mean by misuse:

"But wait," you might say, "isn't app data protected by the OS sandbox?"Technically yes, but let me show you where this data actually ends up.

On Android, SharedPreferences are stored in XML files at:

And the contents look like this:

On iOS, NSUserDefaults are stored in plist files at:

Both locations are easily readable on rooted/jailbroken devices, throughbackup extraction, or with forensic tools. The sandbox provides no protectionagainst these attack vectors.

The rule is simple: SharedPreferences is for preferences (dark mode,language, onboarding completed), not for secrets.

Example output (what an attacker sees after extraction):

2. Local File Storage

Writing files to the app's document or cache directory is another commonpattern. Without encryption, you're essentially creating a readable archive ofsensitive data:

I've seen this pattern in production apps handling financial data, medicalrecords, and legal documents. The developers assumed that because the file wasin "their app's directory," it was safe. It wasn't.

Example output (plain JSON an attacker reads):

3. SQLite Databases

Flutter apps using sqflite or similar packages often create databases that seem more "serious" than SharedPreferences or JSON files. But without encryption, they're just as vulnerable:

The database file at /data/data/com.example.app/databases/user_data.db can be extracted and opened with any SQLite viewer. I once demonstrated this to a client by pulling their entire user database off a test device in under 30 seconds.

Example output (what an attacker dumps):

4. Application Logs

This one catches a lot of developers off guard. Debug logging that seemed harmless during development persists in production builds and can leak sensitive information:

Example console output (credentials visible to anyone with ADB — Android DebugBridge):

Platform-Specific Secure Storage

So what should you use instead? The good news is that both Android and iOS provide solid secure storage mechanisms. The key is understanding how to use them properly from Flutter.

Android: The Keystore System

Android’s Keystore systemarrow-up-right is the gold standard for secure storage on the platform. When available (which ismost modern devices), it provides hardware-backed cryptographic key storage.This means the encryption keys literally never leave a secure hardware module,even the operating system can't extract them.

What makes Android Keystore special:

Feature
Description

Hardware Backing

Keys stored in TEE (Trusted Execution Environment) or StrongBox (Pixel, Samsung Knox)

Key Extraction Prevention

Private keys never leave secure hardware

User Authentication

Can require biometrics/PIN before key use

Key Attestation

Verify key properties with signed certificate chain

Usage Restrictions

Limit key to specific algorithms/purposes

The important thing to understand is that when you use flutter_secure_storageon Android, your data is encrypted with keys that are protected by this hardwaresecurity. Even if an attacker extracts your encrypted data, they can't decryptit without access to the secure hardware—which requires physical possession ofthe specific device.

iOS: Keychain Services

iOS takes a slightly different approach with its Keychain Servicesarrow-up-right. Rather than giving you direct access to encryption keys, the Keychain handles both key management and data storage. You put secrets in, and iOS keeps them encrypted with hardware-protected keys. On devices with a Secure Enclave, cryptographic keys created with the kSecAttrTokenIDSecureEnclavearrow-up-right flag never leave that hardware—but note that general Keychain items are protected by the device’s Data Protection keys, not stored directly inside theEnclave.

One of the most important decisions you'll make with iOS Keychain is choosing the right accessibility levelarrow-up-right. This determines when your data can be accessed and whether it gets included in backups:

Level
Description
Use Case

WhenPasscodeSetThisDeviceOnly

Most restrictive; deleted if passcode removed

Highly sensitive (banking credentials)

WhenUnlockedThisDeviceOnly

Accessible only when unlocked, not backed up

Session tokens, temporary secrets

WhenUnlocked

Accessible when unlocked, backed up

User preferences with sensitive elements

AfterFirstUnlockThisDeviceOnly

Accessible after first unlock, not backed up

Background sync credentials

AfterFirstUnlock

Accessible after first unlock, backed up

Push notification tokens

For most sensitive data, I recommend AfterFirstUnlockThisDeviceOnly. This provides strong protection while still allowing background operations like push notifications to access the data when needed. The "ThisDeviceOnly" suffix means the data won't be included in iCloud backups, which is important for secrets you don't want floating around in the cloud.

Implementing Secure Storage in Flutter

With the platform picture in mind, here's how to put it to work from your Flutter code.

Using flutter_secure_storage

The flutter_secure_storagearrow-up-right package is the standard solution for secure storage in Flutter. It provides aunified API that uses Android Keystore on Android and iOS Keychain on iOS:

Example output:

Secure Database with SQLCipher

For applications that need to store more structured data, plain SQLite isn't enough. The sqflite_sqlcipher package provides transparent database encryption using SQLCipher, which is widely trusted in the security community.

The key insight here is that you need to store the database encryption keys ecurely—which brings us back to flutter_secure_storage. Here's the pattern I recommend:

With this setup, even if someone extracts the database file, they'll just see encrypted gibberish. The decryption key lives in the hardware-backed secure storage, making the data practically unreadable without access to the specific device.

Example output:

Secure File Storage

Sometimes you need to store larger files, documents, images, or exported data, that don't fit well in a database or key-value store. Here's how to handle that securely:

Example output:

Three things in this implementation are worth highlighting:

  1. Unique IV per encryption: A fresh IV is generated for every file. Reusing

  • the same IV with the same key in AES-GCM isn't just suboptimal—it

  • mathematically breaks the security guarantees.

  1. AES-GCM mode: Galois/Counter Mode gives you confidentiality and

  • authenticity in one pass. Tamper with the ciphertext and decryption fails

  • outright rather than returning corrupted data silently.

  1. Secure deletion: secureDelete overwrites the file with random bytes

  • before deleting. Not a perfect guarantee on flash storage (keep reading), but

  • it meaningfully raises the effort required for recovery.

Caveat — flash storage and wear leveling: Modern mobile devices use NAND flash (a solid-state memory technology) with wear-leveling controllers. Overwriting a file does not guarantee the original physical blocks are erased.For the strongest guarantees, rely on full-disk encryptionarrow-up-right (enabled by default on Android 10+ and all iOS devices) so that discarded blocks remain encrypted even if they aren’t zeroed.

Secure Storage Architecture

Individual APIs are one thing, but in a real app you want a single place whereall storage decisions live. The rule I follow: your UI and repository layers should never touch encryption keys or platform storage APIs directly. That concern belongs in a dedicated security layer:

Complete Secure Storage Service

Here's what that security layer looks like in practice. It supports three sensitivity tiers—because a user preference and a banking credential shouldn't be stored the same way:

Usage Example: Authentication Service

Preventing Data Leakage

Secure storage protects data at rest, but sensitive information leaks throughside channels that most developers don't think about until something goes wrong.I've seen each of the following cause real incidents: debug logs submitted insupport tickets, screenshots captured by the OS app switcher, clipboard contentsharvested by other apps.

1. Secure Logging

A print() call that felt harmless in development quietly persists into yourrelease build and ends up in support logs, ADB output, and crash reports. Here'sa logger that makes it structurally difficult to accidentally expose sensitivevalues:

Output (debug console):

This logger does several important things: it completely disables logging inrelease mode, and even in debug mode, it automatically redacts patterns thatlook like tokens, credit cards, SSNs (Social Security Numbers), and passwords.You can expand the pattern matching for your specific use cases.

2. Clipboard Security

When users copy sensitive data (like account numbers or recovery codes), thatdata sits in the system clipboard where any app can read it. Here's how toauto-clear the clipboard after a short period:

Thirty seconds is usually enough time for users to paste what they copied, butshort enough to limit exposure. For extremely sensitive data, you might reducethis to 10-15 seconds.

3. Screenshot Protection

For apps handling sensitive information (banking, health, legal), you shouldprevent screenshots of sensitive screens. Here's how to implement this withplatform channels:

Android Implementation (MainActivity.kt):

On Android, we use the FLAG_SECURE window flag which prevents screenshots andalso hides the app content in the recent apps list:

iOS Implementation (AppDelegate.swift):

iOS doesn't have an exact equivalent, but we can show a blur or overlay when theapp goes to the background (which is when screenshots are typically captured):

4. Secure Memory Handling

This is an advanced topic, but important for truly sensitive data. When youstore a password or key in a Dart string, that string sits in memory until thegarbage collector cleans it up, which might be a while. On a compromised device,memory can be dumped and searched for sensitive patterns.

Here's a pattern for handling sensitive data that clears memory as soon as it'sno longer needed:

Note that Dart strings are immutable, so we can't truly "clear" them—we workwith byte arrays instead. This is a limitation of the language, but using thispattern still reduces the window of exposure.

Example output:

Real Attack Scenarios & Prevention

Abstract advice only gets you so far. Here's what these attacks actually looklike when they happen—and exactly what stops each one.

Scenario 1: Backup Extraction Attack

The attack: An attacker gains access to a user's iCloud or Google account(through phishing, password reuse, etc.) and downloads device backups. Theyextract your app's data from the backup and find authentication tokens stored inplain SharedPreferences.

What happens next: The attacker uses those tokens to access the user'saccount, potentially stealing personal information, making unauthorizedpurchases, or worse.

With the secure implementation, even if the attacker gets the backup, the tokensare encrypted with device-specific keys that aren't included in the backup.

Scenario 2: Root/Jailbreak Data Extraction

The attack: A user's device is compromised by malware (perhaps from amalicious app or sideloaded software). The malware has root access and readsfiles from your app's private directory.

Example output (device security checks):

Scenario 3: Logging Credential Leak

The attack: During a support investigation, someone shares device logs witha third party (or posts them online asking for help). Those logs containauthentication tokens that were printed during debugging and never removed.

This one happens more often than you'd think. I've seen production apps thatlogged full API responses including auth tokens to the console.

Security Checklist

I run through something like this before every major release. It takes twentyminutes and has caught real issues more than once:

Encryption

Access control

Backup security

Logging

Memory

Root/Jailbreak

Data leakage

Conclusion

If there's one thing I hope you take away from this article, it's that datastorage security isn't optional—it's a fundamental responsibility we have to ourusers. When someone trusts your app with their health records, financialinformation, or personal details, they're trusting you to protect that data.Insecure storage betrays that trust.

The good news is that Flutter gives us access to excellent platform-levelsecurity through packages like flutter_secure_storage. The hardware-backedencryption provided by Android Keystore and iOS Keychain is genuinely difficultto break when used correctly. You just need to use it.

Remember: the effort you put into secure storage pays dividends in user trust,regulatory compliance, and avoiding the nightmare of a data breach disclosure.It's one of those areas where doing it right from the start is much easier thanfixing it after a security incident.

In the next article, we'll explore M10: Insufficient Cryptography, wherewe'll dive into proper cryptographic implementations, key management, and thecommon crypto mistakes I see in Flutter applications. Because encryption donewrong can be worse than no encryption at all—it gives you a false sense ofsecurity.

Resources

Stay secure, and remember: when it comes to user data, paranoia is a feature,not a bug. 🔐

Last updated

Was this helpful?