Encrypting Local Data in Mobile Apps: A Practical Guide

Short answer: Encrypting local data in mobile apps means protecting data at rest using platform-native cryptographic APIs like Android EncryptedSharedPreferences or iOS Keychain. For cross-platform frameworks, use libraries like flutter_secure_storage or react-native-keychain. Always store encryption keys in hardware-backed keystores, and add encryption tests to your CI/CD pipeline.

Key takeaways

  • Use platform-native keystores for encryption keys.
  • Flutter: flutter_secure_storage wraps Keychain and KeyStore.
  • React Native: react-native-keychain provides similar abstraction.
  • Encrypt shared preferences and SQLite databases, not just tokens.
  • Add CI/CD steps to verify encryption is enforced.
  • Balance security with performance; benchmark your approach.

If you build mobile apps that store user data locally, encryption isn’t optional. I’ve seen too many apps treat local storage as a dice roll. A lost phone or a compromised device can expose everything from auth tokens to personal notes. Let’s walk through how to encrypt local data in mobile apps the right way, covering Flutter, React Native, and native platforms, plus how to keep your CI/CD pipeline honest about encryption.

Why encrypt local data? (And what happens if you don’t)

Mobile devices get stolen. Apps get reverse-engineered. Shared storage on Android can be read by other apps if you’re not careful. Without encryption, an attacker with physical access can pull plaintext data from the app’s sandbox.

I’ve audited apps where the developer stored API keys in plain SharedPreferences. The fix was straightforward, but the damage would have been real. Encryption isn’t just about compliance (though GDPR and HIPAA demand it). It’s about respecting your users’ privacy. When you encrypt local data, you ensure that even if the device is compromised, the data remains unreadable.

Understanding the mobile encryption stack

Mobile encryption works in layers. At the base, you have hardware-backed secure elements (like the Secure Enclave on iOS or the Trusted Execution Environment on Android). Above that, platform APIs like iOS Keychain and Android Keystore manage keys. Your app sits on top, using these APIs to encrypt and decrypt data.

The golden rule: never roll your own crypto. Use the platform’s built-in primitives. They handle key generation, storage, and rotation with hardware backing. Your job is to use them correctly.

iOS: Keychain and CryptoKit

On iOS, the Keychain is your go-to for storing small secrets like tokens. For larger data, you can use CryptoKit (iOS 13+) for symmetric encryption with AES-GCM. Always store the encryption key in the Keychain with the kSecAttrAccessible attribute set to kSecAttrAccessibleWhenUnlockedThisDeviceOnly so it’s tied to the device.

Android: EncryptedSharedPreferences and Android Keystore

Android’s Jetpack Security library provides EncryptedSharedPreferences, which encrypts both keys and values using AES256/GCM. It’s built on top of the Android Keystore, which keeps the master key in hardware. For file encryption, use EncryptedFile from the same library.

Encrypting local data in Flutter

In Flutter, you have several options. The most popular is flutter_secure_storage. It wraps iOS Keychain and Android’s encrypted shared preferences or EncryptedSharedPreferences depending on version. Here’s a typical usage:

final storage = FlutterSecureStorage();
await storage.write(key: 'auth_token', value: token);
final token = await storage.read(key: 'auth_token');

Under the hood, it encrypts data with AES and stores the key in the platform’s keystore. But be aware: on Android, older versions used a weaker implementation. Always pin to a recent version (4.x or later). Also, the plugin doesn’t encrypt SQLite databases by default. For that, consider sqflite combined with flutter_secure_storage to store the database encryption key, then use sqflite_common_ffi with SQLCipher for full database encryption.

Encrypting local data in React Native

React Native developers often reach for react-native-keychain. It provides a simple API to store credentials securely:

import * as Keychain from 'react-native-keychain';
await Keychain.setGenericPassword('username', 'password');
const credentials = await Keychain.getGenericPassword();

For general data encryption, react-native-encrypted-storage wraps Android’s EncryptedSharedPreferences and iOS Keychain. It’s a drop-in replacement for AsyncStorage. For SQLite encryption, use react-native-sqlite-storage with SQLCipher support.

A common mistake I see: developers encrypt only the access token but leave the user’s profile data in plain AsyncStorage. If a token alone is enough to authenticate, then the token must be encrypted. But don’t stop there. Any personal data—email, phone number, preferences—should also be encrypted.

Comparison: Encryption libraries for Flutter and React Native

LibraryPlatformUse CaseKey Storage
flutter_secure_storageFlutterSmall secrets, tokensiOS Keychain / Android Keystore
sqflite + SQLCipherFlutterEncrypted databasesUser-provided key (store in secure storage)
react-native-keychainReact NativeCredentials, tokensiOS Keychain / Android Keystore
react-native-encrypted-storageReact NativeGeneral key-value encryptioniOS Keychain / Android Keystore

Key management: the hardest part of encryption

Encryption is only as strong as the key management. If an attacker can extract your encryption key, the rest is useless. Here are the rules:

  • Never hardcode keys in source code. I’ve seen keys in plain strings, base64, or even obfuscated. Obfuscation is not encryption.
  • Use hardware-backed keystores. Both iOS Keychain and Android Keystore can generate keys that never leave the secure hardware.
  • Consider key rotation. For long-lived data, have a key version scheme. When you update the app, you can re-encrypt data with a new key.
  • Handle key loss. If the user resets their device, the keystore is wiped. Your app should gracefully handle decryption failures and prompt re-login.

Adding encryption checks to your CI/CD pipeline

Encryption isn’t a set-and-forget feature. As your codebase grows, someone might accidentally store plaintext data. That’s where CI/CD helps.

Here’s a step-by-step approach to integrate encryption validation into your pipeline:

  1. Write unit tests that verify encryption functions work. For example, test that writing a value and reading it back returns the same plaintext, and that the raw storage file contains ciphertext (not the plaintext).
  2. Add static analysis rules to flag usage of non-encrypted storage classes (like SharedPreferences or AsyncStorage) in pull requests. Tools like ESLint with custom plugins can do this.
  3. Run security scans with tools like MobSF (Mobile Security Framework) in your CI. MobSF can detect insecure storage and missing encryption.
  4. Nightly build tests that simulate data extraction from the app’s sandbox and verify that sensitive fields are encrypted.

For a deeper look at securing your app at the API level, check out How to Secure Mobile App APIs Against Common Threats.

Performance tradeoffs: encryption isn’t free

Encryption adds CPU overhead and can increase latency when reading or writing data. For small payloads (tokens, preferences), the hit is negligible. For large files or databases, you’ll notice.

Benchmark your approach. On Android, EncryptedSharedPreferences adds about 4-8 milliseconds per operation. On iOS, Keychain operations are similar. For database encryption with SQLCipher, expect a 5-15% performance hit on queries. That’s usually acceptable for most apps, but if you have heavy local reads (like offline caches), test it on low-end devices.

One trick: use in-memory caching to avoid decrypting the same data repeatedly. Keep an encrypted store as the source of truth, but hold a decrypted cache in memory for the current session.

Common pitfalls and how to avoid them

  • Encrypting then encoding incorrectly. Always store encrypted data as base64 or hex, not raw bytes, to avoid encoding issues.
  • Reusing IVs. For AES-GCM, never reuse the same IV with the same key. Use random IVs generated by your crypto library.
  • Ignoring platform differences. Android and iOS have different key storage guarantees. Don’t assume Android’s Keystore is as secure as iOS’s Secure Enclave. On older Android devices without hardware backing, keys are stored in software – still better than nothing, but weaker.
  • Not testing on real devices. Simulators often skip hardware security. Test encryption on actual devices, especially older ones.

For a more foundational look at mobile app security, you might also read Hello world! (yes, it’s meta, but it documents our starting point).

Wrap up: make encryption a habit

Encrypting local data in mobile apps isn’t hard once you know the right APIs. Start with platform-native keystores for keys, use well-maintained libraries for cross-platform apps, and bake encryption verification into your CI/CD. Your users’ data deserves nothing less. The next time you add a new data field to local storage, ask: should this be encrypted? The answer is almost always yes.

Frequently asked questions

What’s the best way to encrypt local data in a Flutter app?

For small secrets like tokens, use flutter_secure_storage. It stores data encrypted with AES and keys in the platform keystore. For larger data or databases, combine a database library (like sqflite) with SQLCipher, and store the encryption key in flutter_secure_storage.

Is it safe to use AsyncStorage in React Native for sensitive data?

No. AsyncStorage stores data in plaintext on both iOS and Android. Never use it for tokens, passwords, or personal data. Use react-native-encrypted-storage instead, which encrypts data using AES256 with keys stored in the platform keystore.

How does key rotation work for mobile app encryption?

Key rotation means replacing an old encryption key with a new one. To rotate, keep a key version identifier with the encrypted data. Read the old key, decrypt the data, then re-encrypt with the new key. Store the new key in the keystore. Do this during app upgrades or periodically.

What’s the difference between iOS Keychain and Android Keystore?

Both are hardware-backed keystores that store cryptographic keys. iOS Keychain is a database for secrets that can also store small data blobs. Android Keystore manages keys and does crypto operations inside secure hardware. Both prevent key extraction, but Android Keystore on older devices may fall back to software if hardware isn’t available.

How can I test encryption in my CI/CD pipeline?

Add unit tests that encrypt and decrypt data, then assert the ciphertext is different from the original. Use a static analysis tool to flag insecure storage usage. Consider running MobSF in your CI to scan for missing encryption. You can also build an automated test that extracts app data and checks if sensitive fields are encrypted.

Leave a Comment