# OWASP Top 10 For Flutter – M9: Insecure Data Storage in Flutter & Dart (cleaned copy)

Welcome back to our deep dive into the OWASP Mobile Top 10 for Flutter developers.

In earlier parts, we tackled:

* [M1: Mastering Credential Security in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m1-mastering-credential-security-in-flutter)
* [M2: Inadequate Supply Chain Security in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m2-inadequate-supply-chain-security-in-flutter)
* [M3: Insecure Authentication and Authorization in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m3-insecure-authentication-and-authorization-in-flutter)
* [M4: Insufficient Input/Output Validation in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m4-insufficient-input-output-validation-in-flutter)
* [M5: Insecure Communication for Flutter and Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m5-insecure-communication-for-flutter-and-dart)
* [M6: Inadequate Privacy Controls in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m6-inadequate-privacy-controls-in-flutter-and-dart)
* [M7: Insufficient Binary Protection in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m7-insufficient-binary-protection-in-flutter-and-dart)
* [M8: Security Misconfiguration in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m8-security-misconfiguration-in-flutter-and-dart)

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 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 Storage**](https://owasp.org/www-project-mobile-top-10/2023-risks/m9-insecure-data-storage) 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?

![](https://t9012551331.p.clickup-attachments.com/t9012551331/46f0e093-dd75-4d30-8b02-018440269621/data-storage-attack-surface-flowchart-dark.webp)

Let’s get into it.

**Source code.** All code examples from this article are available as a runnable Flutter project on GitHub:

> [flutterengineering\_examples — M9 Insecure Data Storage](https://github.com/mhadaily/flutterengineering_examples/tree/main/OWASP_Top_10_Flutter/m9_insecure_data_storage)

### Understanding the Threat Landscape

Before we get to the solutions, I want to give you a clear picture of what you're 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.

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 [**EASY**](https://owasp.org/www-project-mobile-top-10/2023-risks/m9-insecure-data-storage) 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 predictable patterns:

![](https://t9012551331.p.clickup-attachments.com/t9012551331/61fd624f-4f71-4192-92d5-d6027f28ee3c/security-weaknesses-flowchart-dark.webp)

### Flutter Storage Mechanisms: A Security Deep Dive

Before we get to the solutions, it helps to understand exactly what you're dealing with.

Flutter gives you several built-in ways to store data locally. Each one has a different security profile.

The bad news: three of the four most commonly used options are insecure for sensitive data.

#### 1. SharedPreferences / NSUserDefaults

SharedPreferences is the go-to solution for storing simple key-value pairs in Flutter.

It's convenient, easy to use, and completely insecure for sensitive data.

I see it misused constantly. Developers store auth tokens, API keys, and sometimes even passwords here.

Here's what I mean by misuse:

```dart
// ❌ INSECURE: Storing sensitive data in SharedPreferences
import 'package:shared_preferences/shared_preferences.dart';

class InsecureStorage {
  Future<void> storeCredentials(String username, String password) async {
    final prefs = await SharedPreferences.getInstance();

    // DANGER: Plain text storage!
    await prefs.setString('username', username);
    await prefs.setString('password', password);
    await prefs.setString('auth_token', 'eyJhbGciOiJIUzI1...');
    await prefs.setString('credit_card', '4111-1111-1111-1111');
  }
}
```

"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:

```
/data/data/com.example.app/shared_prefs/FlutterSharedPreferences.xml
```

And the contents look like this:

```xml
<?xml version="1.0" encoding="utf-8"?>
<map>
    <string name="flutter.username">john_doe</string>
    <string name="flutter.password">MySecretPassword123!</string>
    <string name="flutter.auth_token">eyJhbGciOiJIUzI1...</string>
    <string name="flutter.credit_card">4111-1111-1111-1111</string>
</map>
```

**On iOS**, NSUserDefaults are stored in plist files at:

```
/var/mobile/Containers/Data/Application/<UUID>/Library/Preferences/com.example.app.plist
```

Both locations are **easily readable** on rooted/jailbroken devices, through backup extraction, or with forensic tools.

The sandbox provides no protection against 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):**

```
[BAD] SharedPreferences stores data in PLAIN TEXT:
[BAD]   Android → /data/data/<pkg>/shared_prefs/FlutterSharedPreferences.xml
[BAD]   iOS    → /var/mobile/Containers/Data/Application/<UUID>/Library/Preferences/<pkg>.plist
[BAD]
[BAD] Example XML content an attacker would see:
[BAD]   <string name="flutter.username">john_doe</string>
[BAD]   <string name="flutter.password">MySecretPassword123!</string>
[BAD]   <string name="flutter.auth_token">eyJhbGciOiJIUzI1...</string>
[BAD]   <string name="flutter.credit_card">4111-1111-1111-1111</string>
```

#### 2. Local File Storage

Writing files to the app's document or cache directory is another common pattern.

Without encryption, you're essentially creating a readable archive of sensitive data:

```dart
// ❌ INSECURE: Storing sensitive files without encryption
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

class InsecureFileStorage {
  Future<void> saveUserProfile(Map<String, dynamic> profile) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/user_profile.json');

    // DANGER: Plain JSON with sensitive data!
    await file.writeAsString(jsonEncode(profile));
    // Contains: SSN, date of birth, address, etc.
  }

  Future<void> saveMedicalRecords(List<Map<String, dynamic>> records) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/medical_records.json');

    // DANGER: Health data unencrypted!
    await file.writeAsString(jsonEncode(records));
  }
}
```

I've seen this pattern in production apps handling financial data, medical records, and legal documents.

The developers assumed that because the file was in "their app's directory," it was safe. It wasn't.

**Example output (plain JSON an attacker reads):**

```
[BAD] Writing user profile as PLAIN JSON (no encryption):
[BAD]   File: <documents>/user_profile.json
[BAD]   Content:
[BAD]     {
[BAD]       "name": "John Doe",
[BAD]       "ssn": "123-45-6789",
[BAD]       "dob": "1990-01-15",
[BAD]       "address": "123 Main St, Springfield"
[BAD]     }
[BAD]
[BAD] Writing medical records as PLAIN JSON (no encryption):
[BAD]   File: <documents>/medical_records.json
[BAD]   Content:
[BAD]     [
[BAD]       {
[BAD]         "diagnosis": "Type 2 Diabetes",
[BAD]         "medication": "Metformin 500mg"
[BAD]       },
[BAD]       {
[BAD]         "diagnosis": "Hypertension",
[BAD]         "medication": "Lisinopril 10mg"
[BAD]       }
[BAD]     ]
[BAD]
[BAD] ⚠️  Anyone with device access can read this data!
```

#### 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:

```dart
// ❌ INSECURE: Unencrypted SQLite database
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class InsecureDatabase {
  Database? _database;

  Future<Database> get database async {
    if (_database != null) return _database!;

    final dbPath = await getDatabasesPath();
    _database = await openDatabase(
      join(dbPath, 'user_data.db'), // Plain, unencrypted!
      version: 1,
      onCreate: (db, version) async {
        await db.execute('''
          CREATE TABLE users (
            id INTEGER PRIMARY KEY,
            username TEXT,
            password_hash TEXT,  -- Still sensitive!
            ssn TEXT,            -- DANGER: Plaintext SSN
            credit_score INTEGER,
            bank_account TEXT
          )
        ''');
      },
    );
    return _database!;
  }
}
```

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):**

```
[BAD] Unencrypted SQLite database:
[BAD]   Path: /data/data/<pkg>/databases/user_data.db
[BAD]
[BAD]   CREATE TABLE users (
[BAD]     id INTEGER PRIMARY KEY,
[BAD]     username TEXT,
[BAD]     password_hash TEXT,   -- still sensitive!
[BAD]     ssn TEXT,             -- DANGER: Plaintext SSN
[BAD]     credit_score INTEGER,
[BAD]     bank_account TEXT
[BAD]   );
[BAD]
[BAD]   Example row an attacker extracts:
[BAD]   | john_doe | $2b$10$Kx... | 123-45-6789 | 750 | 9876543210 |
[BAD]
[BAD] ⚠️  Database file readable with: sqlite3 user_data.db ".dump"
```

#### 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:

```dart
// ❌ INSECURE: Logging sensitive data
import 'dart:convert';
import 'dart:developer';

class AuthService {
  Future<void> login(String email, String password) async {
    // DANGER: Credentials in logs!
    print('Login attempt: email=$email, password=$password');
    // DANGER: API key in logs
    // debugPrint('API Key: ${ApiConfig.key}');

    try {
      final response = await _apiClient.post('/auth/login', {
        'email': email,
        'password': password,
      });

      // DANGER: Token in logs!
      print('Login successful! Token: ${response.token}');
      log('User data: ${jsonEncode(response.user)}');
    } catch (e) {
      // DANGER: Error might contain sensitive data
      print('Login failed: $e');
    }
  }
}
```

**Example console output (credentials visible to anyone with ADB — Android Debug Bridge):**

```
[BAD] Login attempt: email=user@example.com, password=s3cretP@ss!
[BAD] Got token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkw.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
[BAD] API Key: sk-live-abc123xyz789
[BAD] User data: {email: user@example.com, ssn: 123-45-6789, password: secret123}
```

### 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 system](https://developer.android.com/training/articles/keystore) is the gold standard for secure storage on the platform.

When available (most modern devices), it provides hardware-backed cryptographic key storage.

This means encryption keys never leave a secure hardware module. Even the operating system can't extract them.

![](https://t9012551331.p.clickup-attachments.com/t9012551331/fcc34169-c7b7-47c2-bb15-c3890ddde0fb/android-keystore-architecture-flowchart-dark.webp)

**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_storage` on Android, your data is encrypted with keys protected by hardware security.

Even if an attacker extracts your encrypted data, they can't decrypt it without the secure hardware.

That requires physical possession of the specific device.

#### iOS: Keychain Services

iOS takes a slightly different approach with [Keychain Services](https://developer.apple.com/documentation/security/keychain_services).

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 [`kSecAttrTokenIDSecureEnclave`](https://developer.apple.com/documentation/security/ksecattrtokenidsecureenclave) flag never leave that hardware.

General Keychain items are protected by the device’s Data Protection keys, not stored directly inside the Enclave.

![](https://t9012551331.p.clickup-attachments.com/t9012551331/bd264e60-80d7-49aa-9d1c-c33e876dd218/ios-keychain-architecture-flowchart-dark.webp)

One of the most important decisions you'll make with iOS Keychain is choosing the right [accessibility level](https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility).

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.

The "ThisDeviceOnly" suffix means the data won't be included in iCloud backups.

### 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_storage`](https://pub.dev/packages/flutter_secure_storage) package is the standard solution for secure storage in Flutter.

It provides a unified API that uses Android Keystore on Android and iOS Keychain on iOS:

```dart
// ✅ SECURE: Using flutter_secure_storage
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  late final FlutterSecureStorage _storage;

  SecureStorageService() {
    // Configure with secure options
    _storage = const FlutterSecureStorage(
      aOptions: AndroidOptions(
        // Uses Android's EncryptedSharedPreferences under the hood
        encryptedSharedPreferences: true,
      ),
      iOptions: IOSOptions(
        // High security: requires passcode, device-only
        accessibility: KeychainAccessibility.passcode,
        // Don't sync to iCloud Keychain
        synchronizable: false,
      ),
    );
  }

  Future<void> storeAuthToken(String token) async {
    await _storage.write(key: 'auth_token', value: token);
  }

  Future<String?> getAuthToken() async {
    return _storage.read(key: 'auth_token');
  }

  Future<void> clearAll() async {
    await _storage.deleteAll();
  }

  Future<bool> hasKey(String key) async {
    return _storage.containsKey(key: key);
  }
}
```

**Example output:**

```
[SecureStorage] ✅ Auth token stored (encrypted, hardware-backed).
[SecureStorage] ✅ Auth token retrieved (length: 36).
[SecureStorage] ✅ Refresh token stored (biometric-protected).
[SecureStorage] ✅ All secure storage cleared.
```

#### Secure Database with SQLCipher

For apps that need structured storage, plain SQLite isn't enough.

`sqflite_sqlcipher` provides transparent database encryption using SQLCipher.

The key insight: store the database encryption key securely.

That brings us back to `flutter_secure_storage`.

```dart
// ✅ SECURE: Encrypted SQLite database
import 'dart:math';
import 'package:sqflite_sqlcipher/sqflite.dart';
import 'package:path/path.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureDatabase {
  Database? _database;
  final _secureStorage = const FlutterSecureStorage();
  static const _dbKeyStorageKey = 'database_encryption_key';

  Future<String> _getDatabaseKey() async {
    String? key = await _secureStorage.read(key: _dbKeyStorageKey);

    if (key == null) {
      key = _generateSecureKey(32);
      await _secureStorage.write(key: _dbKeyStorageKey, value: key);
    }

    return key;
  }

  String _generateSecureKey(int length) {
    const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#\$%^&*';
    final random = Random.secure();
    return List.generate(length, (_) => charset[random.nextInt(charset.length)]).join();
  }

  Future<Database> get database async {
    if (_database != null) return _database!;

    final dbPath = await getDatabasesPath();
    final encryptionKey = await _getDatabaseKey();

    _database = await openDatabase(
      join(dbPath, 'secure_user_data.db'),
      version: 1,
      password: encryptionKey,
      onCreate: (db, version) async {
        await db.execute('''
          CREATE TABLE sensitive_data (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            data_type TEXT NOT NULL,
            encrypted_value TEXT NOT NULL,
            created_at INTEGER NOT NULL,
            updated_at INTEGER NOT NULL
          )
        ''');

        await db.execute(
          'CREATE INDEX idx_data_type ON sensitive_data(data_type)'
        );
      },
    );

    return _database!;
  }

  Future<void> close() async {
    await _database?.close();
    _database = null;
  }
}
```

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.

**Example output:**

```
[SecureDB] ✅ Generated encryption key (length: 32).
[SecureDB] ✅ Key stored in flutter_secure_storage (hardware-backed).
[SecureDB] ✅ Database opened with SQLCipher encryption.
[SecureDB]    Path: <databases>/secure_user_data.db
[SecureDB]    Cipher: AES-256 (via SQLCipher)
[SecureDB]    Key storage: database_encryption_key → Keystore/Keychain
```

#### Secure File Storage

Sometimes you need to store larger files, documents, images, or exported data.

Here’s how to handle that securely:

```dart
// ✅ SECURE: Encrypted file storage
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:path_provider/path_provider.dart';

class SecureFileStorage {
  final _secureStorage = const FlutterSecureStorage();
  static const _fileKeyStorageKey = 'file_encryption_key';

  Future<encrypt.Key> _getEncryptionKey() async {
    String? keyBase64 = await _secureStorage.read(key: _fileKeyStorageKey);

    if (keyBase64 == null) {
      final key = encrypt.Key.fromSecureRandom(32);
      keyBase64 = key.base64;
      await _secureStorage.write(key: _fileKeyStorageKey, value: keyBase64);
      return key;
    }

    return encrypt.Key.fromBase64(keyBase64);
  }

  /// Encrypt and save data to a file
  Future<void> saveEncryptedFile(String filename, String data) async {
    final key = await _getEncryptionKey();
    final iv = encrypt.IV.fromSecureRandom(16);

    final encrypter = encrypt.Encrypter(
      encrypt.AES(key, mode: encrypt.AESMode.gcm),
    );

    final encrypted = encrypter.encrypt(data, iv: iv);

    final combined = {
      'iv': iv.base64,
      'data': encrypted.base64,
    };

    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$filename.enc');
    await file.writeAsString(jsonEncode(combined));
  }

  /// Read and decrypt data from a file
  Future<String?> readEncryptedFile(String filename) async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      final file = File('${directory.path}/$filename.enc');

      if (!await file.exists()) return null;

      final content = await file.readAsString();
      final combined = jsonDecode(content) as Map<String, dynamic>;

      final key = await _getEncryptionKey();
      final iv = encrypt.IV.fromBase64(combined['iv'] as String);
      final encrypter = encrypt.Encrypter(
        encrypt.AES(key, mode: encrypt.AESMode.gcm),
      );

      return encrypter.decrypt64(combined['data'] as String, iv: iv);
    } catch (_) {
      // Decryption failed: tampered data, wrong key, or corrupted file.
      // Avoid logging the error — it may contain ciphertext fragments.
      return null;
    }
  }

  /// Securely delete a file (overwrite before deletion)
  Future<void> secureDelete(String filename) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$filename.enc');

    if (await file.exists()) {
      final length = await file.length();
      final random = Random.secure();
      final randomData = List.generate(length, (_) => random.nextInt(256));
      await file.writeAsBytes(Uint8List.fromList(randomData));

      await file.delete();
    }
  }
}
```

**Example output:**

```
[SecureFile] ✅ AES-256-GCM encryption:
[SecureFile]    Plaintext length : 48 bytes
[SecureFile]    IV  (base64)     : <random-base64>
[SecureFile]    Ciphertext (b64) : <random-base64>…
[SecureFile]    Key stored in    : flutter_secure_storage
[SecureFile]
[SecureFile]    File written: <documents>/medical.enc
[SecureFile]    Format: { "iv": "<base64>", "data": "<base64>" }

[SecureFile] 🗑️  Secure delete:
[SecureFile]    Step 1: Overwrite 2048 bytes with random data
[SecureFile]    Step 2: Delete file from filesystem
[SecureFile]    ⚠️  Note: flash wear-leveling means original blocks
[SecureFile]           may persist — rely on full-disk encryption too.
```

Three things in this implementation are worth highlighting:

1. **Unique IV per encryption:** generate a fresh IV for every file. Never reuse an IV with the same key in AES-GCM.
2. **AES-GCM mode:** AES-GCM gives confidentiality and authenticity in one pass.
3. **Secure deletion:** overwriting raises the recovery effort. It is not a perfect guarantee on flash.

**Caveat — flash storage and wear leveling**: mobile devices use NAND flash with wear-leveling controllers.

Overwriting a file does not guarantee the original physical blocks are erased.

For the strongest guarantees, rely on [full-disk encryption](https://source.android.com/docs/security/features/encryption) (enabled by default on Android 10+ and all iOS devices).

### Secure Storage Architecture

Individual APIs are one thing. In a real app you want a single place where storage decisions live.

The rule I follow: UI and repository layers should never touch encryption keys or platform storage APIs directly.

That concern belongs in a dedicated security layer:

![](https://t9012551331.p.clickup-attachments.com/t9012551331/e7f835f5-1656-40ff-bdc3-f53cf8618ee6/secure-storage-architecture-flowchart-dark.webp)

#### 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:

```dart
// ✅ SECURE: Comprehensive secure storage service
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:local_auth/local_auth.dart';

enum StorageSensitivity {
  /// Standard encryption, no biometrics
  standard,

  /// Requires biometric authentication
  biometric,

  /// Highest security: biometrics + device-only storage
  critical,
}

class SecureStorageService {
  final FlutterSecureStorage _standardStorage;
  final FlutterSecureStorage _biometricStorage;
  final FlutterSecureStorage _criticalStorage;
  final LocalAuthentication _localAuth;

  SecureStorageService()
      : _standardStorage = const FlutterSecureStorage(
          aOptions: AndroidOptions(
            encryptedSharedPreferences: true,
          ),
          iOptions: IOSOptions(
            accessibility: KeychainAccessibility.first_unlock,
          ),
        ),
        _biometricStorage = FlutterSecureStorage(
          aOptions: AndroidOptions.biometric(
            enforceBiometrics: true,
            biometricPromptTitle: 'Verify your identity',
          ),
          iOptions: const IOSOptions(
            accessibility: KeychainAccessibility.unlocked,
          ),
        ),
        _criticalStorage = FlutterSecureStorage(
          aOptions: AndroidOptions.biometric(
            enforceBiometrics: true,
            biometricPromptTitle: 'High security verification required',
          ),
          iOptions: const IOSOptions(
            accessibility: KeychainAccessibility.passcode,
            synchronizable: false,
          ),
        ),
        _localAuth = LocalAuthentication();

  FlutterSecureStorage _getStorage(StorageSensitivity sensitivity) {
    switch (sensitivity) {
      case StorageSensitivity.standard:
        return _standardStorage;
      case StorageSensitivity.biometric:
        return _biometricStorage;
      case StorageSensitivity.critical:
        return _criticalStorage;
    }
  }

  Future<void> write({
    required String key,
    required String value,
    StorageSensitivity sensitivity = StorageSensitivity.standard,
  }) async {
    final storage = _getStorage(sensitivity);
    await storage.write(key: key, value: value);
  }

  Future<String?> read({
    required String key,
    StorageSensitivity sensitivity = StorageSensitivity.standard,
  }) async {
    final storage = _getStorage(sensitivity);
    return storage.read(key: key);
  }

  Future<void> writeObject<T>({
    required String key,
    required T value,
    required Map<String, dynamic> Function(T) toJson,
    StorageSensitivity sensitivity = StorageSensitivity.standard,
  }) async {
    final jsonString = jsonEncode(toJson(value));
    await write(key: key, value: jsonString, sensitivity: sensitivity);
  }

  Future<T?> readObject<T>({
    required String key,
    required T Function(Map<String, dynamic>) fromJson,
    StorageSensitivity sensitivity = StorageSensitivity.standard,
  }) async {
    final jsonString = await read(key: key, sensitivity: sensitivity);
    if (jsonString == null) return null;

    try {
      final json = jsonDecode(jsonString) as Map<String, dynamic>;
      return fromJson(json);
    } catch (e) {
      debugPrint('Failed to parse stored object: $e');
      return null;
    }
  }

  Future<void> delete({
    required String key,
    StorageSensitivity sensitivity = StorageSensitivity.standard,
  }) async {
    final storage = _getStorage(sensitivity);
    await storage.delete(key: key);
  }

  Future<bool> canUseBiometrics() async {
    try {
      final isAvailable = await _localAuth.canCheckBiometrics;
      final isDeviceSupported = await _localAuth.isDeviceSupported();
      return isAvailable && isDeviceSupported;
    } catch (_) {
      return false;
    }
  }

  Future<void> clearAll() async {
    await Future.wait([
      _standardStorage.deleteAll(),
      _biometricStorage.deleteAll(),
      _criticalStorage.deleteAll(),
    ]);
  }
}
```

#### Usage Example: Authentication Service

```dart
// ✅ SECURE: Using secure storage for authentication
class AuthenticationRepository {
  final SecureStorageService _storage;

  static const _accessTokenKey = 'access_token';
  static const _refreshTokenKey = 'refresh_token';
  static const _userProfileKey = 'user_profile';
  static const _bankingCredentialsKey = 'banking_credentials';

  AuthenticationRepository(this._storage);

  Future<void> storeTokens({
    required String accessToken,
    required String refreshToken,
  }) async {
    await _storage.write(
      key: _accessTokenKey,
      value: accessToken,
      sensitivity: StorageSensitivity.standard,
    );

    await _storage.write(
      key: _refreshTokenKey,
      value: refreshToken,
      sensitivity: StorageSensitivity.biometric,
    );
  }

  Future<String?> getAccessToken() async {
    return _storage.read(key: _accessTokenKey);
  }

  Future<String?> getRefreshToken() async {
    return _storage.read(
      key: _refreshTokenKey,
      sensitivity: StorageSensitivity.biometric,
    );
  }

  Future<void> storeBankingCredentials(BankingCredentials credentials) async {
    await _storage.writeObject(
      key: _bankingCredentialsKey,
      value: credentials,
      toJson: (c) => c.toJson(),
      sensitivity: StorageSensitivity.critical,
    );
  }

  Future<BankingCredentials?> getBankingCredentials() async {
    return _storage.readObject(
      key: _bankingCredentialsKey,
      fromJson: BankingCredentials.fromJson,
      sensitivity: StorageSensitivity.critical,
    );
  }

  Future<void> logout() async {
    await _storage.clearAll();
  }
}

class BankingCredentials {
  final String accountNumber;
  final String routingNumber;
  final DateTime lastAccessed;

  BankingCredentials({
    required this.accountNumber,
    required this.routingNumber,
    required this.lastAccessed,
  });

  Map<String, dynamic> toJson() => {
    'accountNumber': accountNumber,
    'routingNumber': routingNumber,
    'lastAccessed': lastAccessed.toIso8601String(),
  };

  factory BankingCredentials.fromJson(Map<String, dynamic> json) {
    return BankingCredentials(
      accountNumber: json['accountNumber'] as String,
      routingNumber: json['routingNumber'] as String,
      lastAccessed: DateTime.parse(json['lastAccessed'] as String),
    );
  }
}
```

### Preventing Data Leakage

Secure storage protects data at rest, but sensitive information leaks through side channels.

I've seen each of the following cause real incidents: debug logs submitted in support tickets, screenshots captured by the OS app switcher, clipboard content harvested by other apps.

#### 1. Secure Logging

A `print()` call that felt harmless in development quietly persists into your release build.

It ends up in support logs, ADB output, and crash reports.

Here’s a logger that makes it structurally difficult to accidentally expose sensitive values:

```dart
// ✅ SECURE: Safe logging that strips sensitive data
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';

class SecureLogger {
  static final Set<String> _sensitiveKeys = {
    'password',
    'token',
    'secret',
    'key',
    'authorization',
    'credit_card',
    'ssn',
    'pin',
    'cvv',
  };

  static void log(String message, {Object? data}) {
    if (kReleaseMode) return;

    final sanitizedMessage = _sanitize(message);
    final sanitizedData = data != null ? _sanitizeObject(data) : null;

    developer.log(
      sanitizedMessage,
      name: 'APP',
      error: sanitizedData,
    );
  }

  static String _sanitize(String input) {
    String result = input;

    result = result.replaceAll(
      RegExp(r'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*'),
      '[REDACTED_JWT]',
    );

    result = result.replaceAll(
      RegExp(r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b'),
      '[REDACTED_CARD]',
    );

    result = result.replaceAll(
      RegExp(r'\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b'),
      '[REDACTED_SSN]',
    );

    result = result.replaceAll(
      RegExp(r'password["\s:=]+[^\s,}"]+', caseSensitive: false),
      'password=[REDACTED]',
    );

    return result;
  }

  static Object? _sanitizeObject(Object obj) {
    if (obj is Map) {
      return obj.map((key, value) {
        final keyLower = key.toString().toLowerCase();
        if (_sensitiveKeys.any((k) => keyLower.contains(k))) {
          return MapEntry(key, '[REDACTED]');
        }
        return MapEntry(key, _sanitizeObject(value));
      });
    } else if (obj is List) {
      return obj.map(_sanitizeObject).toList();
    } else if (obj is String) {
      return _sanitize(obj);
    }
    return obj;
  }
}

void example() {
  SecureLogger.log('User login', data: {
    'email': 'user@example.com',
    'password': 'secret123',
    'token': 'eyJhbGc...',
  });
}
```

**Output** (debug console):

```
[SecureLogger] Input with sensitive data:
[SecureLogger]   Raw : User login: email=user@example.com, password=secret123, token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkw.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U, card=4111-1111-1111-1111, ssn=123-45-6789
[SecureLogger]   Safe: User login: email=user@example.com, password=[REDACTED], token=[REDACTED_JWT], card=[REDACTED_CARD], ssn=[REDACTED_SSN]

[SecureLogger] Map sanitization:
[SecureLogger]   Raw : {email: user@example.com, password: secret123, token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkw.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U}
[SecureLogger]   Safe: {email: user@example.com, password: [REDACTED], token: [REDACTED]}
```

#### 2. Clipboard Security

When users copy sensitive data (account numbers, recovery codes), that data sits in the system clipboard.

Any app can read it.

Auto-clear the clipboard after a short period:

```dart
// ✅ SECURE: Auto-clear clipboard for sensitive data
import 'dart:async';
import 'package:flutter/services.dart';

class SecureClipboard {
  static Timer? _clearTimer;

  static Future<void> copySecure(
    String data, {
    Duration clearAfter = const Duration(seconds: 30),
  }) async {
    await Clipboard.setData(ClipboardData(text: data));

    _clearTimer?.cancel();

    _clearTimer = Timer(clearAfter, () async {
      await Clipboard.setData(const ClipboardData(text: ''));
    });
  }

  static Future<void> clear() async {
    _clearTimer?.cancel();
    await Clipboard.setData(const ClipboardData(text: ''));
  }
}
```

#### 3. Screenshot Protection

For apps handling sensitive information (banking, health, legal), you should prevent screenshots of sensitive screens.

```dart
// ✅ SECURE: Prevent screenshots of sensitive screens
import 'package:flutter/services.dart';

class ScreenshotProtection {
  static const _channel = MethodChannel('com.example.app/security');

  static Future<void> enable() async {
    try {
      await _channel.invokeMethod('enableScreenshotProtection');
    } on PlatformException catch (e) {
      // Avoid logging sensitive context
      // debugPrint('Failed to enable screenshot protection: $e');
    }
  }

  static Future<void> disable() async {
    try {
      await _channel.invokeMethod('disableScreenshotProtection');
    } on PlatformException catch (e) {
      // Avoid logging sensitive context
      // debugPrint('Failed to disable screenshot protection: $e');
    }
  }
}
```

**Android Implementation** (`MainActivity.kt`):

```kotlin
import android.view.WindowManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.example.app/security"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "enableScreenshotProtection" -> {
                    window.setFlags(
                        WindowManager.LayoutParams.FLAG_SECURE,
                        WindowManager.LayoutParams.FLAG_SECURE
                    )
                    result.success(null)
                }
                "disableScreenshotProtection" -> {
                    window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
                    result.success(null)
                }
                else -> result.notImplemented()
            }
        }
    }
}
```

**iOS Implementation** (`AppDelegate.swift`):

```swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private var securityView: UIView?

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(
            name: "com.example.app/security",
            binaryMessenger: controller.binaryMessenger
        )

        channel.setMethodCallHandler { [weak self] (call, result) in
            switch call.method {
            case "enableScreenshotProtection":
                self?.enableScreenshotProtection()
                result(nil)
            case "disableScreenshotProtection":
                self?.disableScreenshotProtection()
                result(nil)
            default:
                result(FlutterMethodNotImplemented)
            }
        }

        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    private func enableScreenshotProtection() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(hideContent),
            name: UIApplication.willResignActiveNotification,
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(showContent),
            name: UIApplication.didBecomeActiveNotification,
            object: nil
        )
    }

    private func disableScreenshotProtection() {
        NotificationCenter.default.removeObserver(
            self,
            name: UIApplication.willResignActiveNotification,
            object: nil
        )
        NotificationCenter.default.removeObserver(
            self,
            name: UIApplication.didBecomeActiveNotification,
            object: nil
        )
        showContent()
    }

    @objc private func hideContent() {
        securityView = UIView(frame: window?.bounds ?? .zero)
        securityView?.backgroundColor = .white
        window?.addSubview(securityView!)
    }

    @objc private func showContent() {
        securityView?.removeFromSuperview()
        securityView = nil
    }
}
```

#### 4. Secure Memory Handling

This is an advanced topic, but important for truly sensitive data.

When you store a password or key in a Dart string, it sits in memory until the garbage collector clears it.

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’s no longer needed:

```dart
// ✅ SECURE: Clear sensitive data from memory
import 'dart:typed_data';

class SecureMemory {
  static Uint8List clearBytes(Uint8List data) {
    for (var i = 0; i < data.length; i++) {
      data[i] = 0;
    }
    return data;
  }

  static SecureString createSecureString(String value) {
    return SecureString(value);
  }
}

class SecureString {
  late Uint8List _data;
  bool _isCleared = false;

  SecureString(String value) {
    _data = Uint8List.fromList(value.codeUnits);
  }

  String get value {
    if (_isCleared) {
      throw StateError('SecureString has been cleared');
    }
    return String.fromCharCodes(_data);
  }

  void clear() {
    if (!_isCleared) {
      SecureMemory.clearBytes(_data);
      _isCleared = true;
    }
  }
}

class LoginService {
  Future<void> login(String email, SecureString password) async {
    try {
      await _api.login(email, password.value);
    } finally {
      password.clear();
    }
  }
}
```

Note: Dart strings are immutable, so we can’t truly "clear" them.

This pattern still reduces the exposure window.

**Example output:**

```
[SecureMemory] Created SecureString: [SecureString(13 bytes)]
[SecureMemory] Value accessible: 13 chars
[SecureMemory] (simulating API login call…)
[SecureMemory] After clear(): [CLEARED]
[SecureMemory] ✅ Access denied: Bad state: SecureString has been cleared
```

### Real Attack Scenarios & Prevention

Abstract advice only gets you so far.

Here’s what these attacks actually look like, and what stops each one.

#### Scenario 1: Backup Extraction Attack

**The attack:** an attacker gains access to a user's iCloud or Google account (phishing, password reuse, etc.).

They download device backups, extract your app's data, and find authentication tokens stored in plain SharedPreferences.

**What happens next:** the attacker uses those tokens to access the user's account. They steal personal information or make unauthorized purchases.

```dart
// ❌ VULNERABLE: Data included in backups
import 'package:shared_preferences/shared_preferences.dart';

class VulnerableStorage {
  Future<void> saveTokens(String accessToken, String refreshToken) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('access_token', accessToken);
    await prefs.setString('refresh_token', refreshToken);
  }
}

// ✅ SECURE: Exclude from backups
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorage {
  final _storage = const FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: KeychainAccessibility.passcode,
      synchronizable: false,
    ),
  );

  Future<void> saveTokens(String accessToken, String refreshToken) async {
    await _storage.write(key: 'access_token', value: accessToken);
    await _storage.write(key: 'refresh_token', value: refreshToken);
  }
}
```

With the secure implementation, even if the attacker gets the backup, the tokens remain device-bound.

#### Scenario 2: Root/Jailbreak Data Extraction

**The attack:** a user’s device is compromised by malware.

The malware has privileged access and reads files from your app's private directory.

```dart
// ✅ SECURE: Detect compromised device and respond appropriately
// Uses freeRASP — callback names may differ across versions.
// See https://pub.dev/packages/freerasp for the latest API.
import 'package:freerasp/freerasp.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecurityService {
  bool _isDeviceSecure = true;

  Future<void> initialize() async {
    final config = TalsecConfig(
      androidConfig: AndroidConfig(
        packageName: 'com.example.app',
        signingCertHashes: ['your_cert_hash'],
        supportedStores: ['com.sec.android.app.samsungapps'],
      ),
      iosConfig: IOSConfig(
        bundleIds: ['com.example.app'],
        teamId: 'YOUR_TEAM_ID',
      ),
      watcherMail: 'security@example.com',
    );

    final callback = ThreatCallback(
      onPrivilegedAccess: _onRootOrJailbreakDetected,
      onAppIntegrity: _onTamperDetected,
      onDebug: () {},
      onSimulator: () {},
      onUnofficialStore: () {},
      onHooks: () {},
      onDeviceBinding: () {},
      onObfuscationIssues: () {},
    );

    Talsec.instance.attachListener(callback);
    await Talsec.instance.start(config);
  }

  void _onRootOrJailbreakDetected() {
    _isDeviceSecure = false;
    _clearSensitiveData();
    _showSecurityWarning();
  }

  void _onTamperDetected() {
    _isDeviceSecure = false;
    _clearSensitiveData();
    _blockAccess();
  }

  Future<void> _clearSensitiveData() async {
    const storage = FlutterSecureStorage();
    await storage.deleteAll();
  }

  void _showSecurityWarning() {
    // Alert user about compromised device
  }

  void _blockAccess() {
    // Prevent access to sensitive features
  }

  bool get canAccessSensitiveData => _isDeviceSecure;
}
```

**Example output:**

```
[Security] Running device security checks…
[Security]   ✅ Root/jailbreak: not detected
[Security]   ✅ App integrity: valid
[Security]   ✅ Debug mode: not attached
[Security]   ✅ Emulator: not detected
[Security]   Device is secure — sensitive features enabled.

[Security] Simulating root/jailbreak detection…
[Security]   🚨 Root/jailbreak DETECTED!
[Security]   🗑️  Sensitive data cleared from secure storage.
[Security]   🔒 Sensitive features BLOCKED.
[Security]   canAccessSensitiveData = false
```

#### Scenario 3: Logging Credential Leak

**The attack:** during a support investigation, someone shares device logs with a third party.

Those logs contain authentication tokens that were printed during debugging and never removed.

```dart
// ❌ VULNERABLE: Logging sensitive data
class VulnerableAuthService {
  Future<void> login(String email, String password) async {
    print('Attempting login for $email with password $password');

    final response = await _api.login(email, password);
    print('Got token: ${response.token}');
  }
}

// ✅ SECURE: No sensitive data in logs
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';

class SecureAuthService {
  final _logger = SecureLogger();

  Future<void> login(String email, String password) async {
    _logger.info('Attempting login for user');

    final response = await _api.login(email, password);
    _logger.info('Login successful');

    // Never log tokens or credentials
  }
}

class SecureLogger {
  void info(String message) {
    if (!kReleaseMode) {
      developer.log('[INFO] $message');
    }
  }

  void error(String message, [Object? error]) {
    if (!kReleaseMode) {
      developer.log('[ERROR] $message', error: error);
    }
  }
}
```

### Security Checklist

I run through something like this before every major release.

It takes twenty minutes and has caught real issues more than once.

#### Encryption

* [ ] All sensitive data encrypted at rest.
* [ ] Platform secure storage used (Keychain/Keystore).
* [ ] Encryption keys stored securely (never hardcoded).
* [ ] Strong encryption algorithms used (for example, AES-256-GCM).

#### Access control

* [ ] Biometric protection for critical data.
* [ ] Sensitive data cleared on failed authentication attempts.
* [ ] Session data expires.

#### Backup security

* [ ] Sensitive data excluded from cloud backups.
* [ ] Device-only storage for critical secrets.

#### Logging

* [ ] No sensitive data in logs.
* [ ] Production logging minimized.
* [ ] Log files properly secured.

#### Memory

* [ ] Sensitive data cleared after use.
* [ ] No credential caching.

#### Root/Jailbreak

* [ ] Detection implemented.
* [ ] Appropriate response to compromised devices.

#### Data leakage

* [ ] Screenshot protection for sensitive screens.
* [ ] Clipboard auto-clear implemented.
* [ ] Keyboard caching disabled for sensitive fields.

### Conclusion

Data storage security isn't optional.

When someone trusts your app with health records or financial information, they're trusting you to protect that data.

The good news is Flutter gives us access to excellent platform-level security through packages like `flutter_secure_storage`.

Hardware-backed encryption from Android Keystore and iOS Keychain is genuinely difficult to break when used correctly.

Remember: it’s much easier to do this right from the start than fix it after an incident.

### Resources

* [OWASP Mobile Top 10 2024 - M9: Insecure Data Storage](https://owasp.org/www-project-mobile-top-10/2023-risks/m9-insecure-data-storage)
* [flutter\_secure\_storage](https://pub.dev/packages/flutter_secure_storage)
* [sqflite\_sqlcipher](https://pub.dev/packages/sqflite_sqlcipher)
* [encrypt](https://pub.dev/packages/encrypt)
* [Android Keystore System](https://developer.android.com/training/articles/keystore)
* [Android EncryptedSharedPreferences (Jetpack Security)](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences)
* [iOS Keychain Services](https://developer.apple.com/documentation/security/keychain_services)
* [Apple Data Protection Overview](https://support.apple.com/guide/security/protecting-keys-in-alternate-boot-modes-sec30bbfc4e2/web)
* [freeRASP Flutter](https://pub.dev/packages/freerasp)

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m9-insecure-data-storage-in-flutter-and-dart-cleaned-copy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
