App protection

Data at rest encryption for mobile apps

How to evaluate data at rest encryption for mobile apps on Android and iOS: inventory the data, own the key lifecycle and know where plaintext still appears.

Data at rest encryption for a mobile app means that anything the app writes to the device, whether files, databases, preferences or cached responses, is unreadable without a key the app controls. To evaluate it, ask three questions in order: which data needs it, who creates and holds the keys, and when the data must be plaintext anyway. Android and iOS already encrypt storage at the operating-system level; this article covers what app-layer encryption adds on top, where it stops, and how to review it per platform.

Start with a data inventory, not a cipher

Most encryption reviews begin with the algorithm. They should begin with a list. Every mobile app writes more than its developers expect: HTTP caches, web view storage, crash logs, keyboard caches, screenshots of the last screen and preferences written by third-party libraries.

Walk the app’s sandbox on a test device after a full user session and record every file and store. For each one, name the data, its storage boundary and the protection it needs.

Data itemWhere it livesSensitivitySuggested protection
Refresh tokenKeychain item or Keystore-wrapped preferenceHighPlatform key store
Signing key for API requestsKeystore or Secure Enclave, non-exportableHighPlatform key store
Account statements (PDF)App documents directoryHighApp-layer encryption
Local message databaseSQLite fileHighApp-layer encryption
User settingsPreferences or user defaultsLowPlatform storage encryption
Cached imagesCaches directoryLowPlatform storage encryption
Cached API responses with personal dataHTTP cacheMediumApp-layer encryption
One-time passcodeMemory onlyHighDo not store
Full card numberNot needed on deviceHighDo not store
Example inventory: items per protection approach
App-layer encryption3Platform key store2Platform storage encryption2Do not store2

Counts come from the example inventory table above, to show how such a table reads. Your own inventory will differ.

The last category matters most. The cheapest data to protect is data the app never writes. If an item can stay on the server, or live only in memory for one screen, remove it from the inventory before choosing a cipher.

What the platforms already give you

Both platforms encrypt user storage, but they do it differently, and the details decide what an app still has to do.

Android

  • File-based encryption with credential-encrypted and device-encrypted storage
  • Credential-encrypted storage is available after the user first unlocks the device
  • Android Keystore keeps keys non-exportable, backed by a trusted execution environment where the device has one
  • StrongBox keys live in a separate secure element on devices that include it

iOS

  • Data Protection classes set per file, from Complete to None
  • Default class for app files is available after the first unlock
  • Keychain items carry their own accessibility class, including this-device-only variants
  • Secure Enclave holds elliptic-curve keys used to sign or to wrap a small symmetric key
The two platforms reach a similar goal by different mechanisms. Review each one on its own terms.
Platform storage protection milestones
  1. 2010

    iOS 4 introduces Data Protection

    Files can be tied to keys derived from the device passcode.

  2. 2013

    iOS 7 changes the default class

    App files default to protection until the first user authentication; the iPhone 5s ships with the Secure Enclave.

  3. 2016

    Android 7.0 adds file-based encryption

    Direct Boot separates device-encrypted and credential-encrypted storage.

  4. 2018

    Android 9 adds StrongBox

    Keystore keys can be generated in a dedicated secure element on supported hardware.

  5. 2019

    Android 10 and iOS 13

    Devices launching with Android 10 must use file-based encryption; iOS 13 introduces CryptoKit.

Key lifecycle: generation, access, rotation, recovery

Encryption is only as strong as the place the key lives. A key written into the app package, a preference file or a string constant is readable by anyone who can read the app. The rule is simple: keys are generated on the device, inside the platform key store, and never leave it.

A common and sound pattern is envelope encryption. A non-exportable key in the platform key store wraps a data key; the data key encrypts the files. The app stores only the wrapped data key.

  1. Generate wrapping key

    Created inside Android Keystore or protected by the Secure Enclave; non-exportable.

  2. Wrap a data key

    A random symmetric data key is encrypted by the wrapping key and stored.

  3. Encrypt the data

    Files and database pages are encrypted with the data key.

  4. Unwrap on use

    The data key is unwrapped into memory when a screen needs the data.

  5. Plaintext window

    Hooking or a debugger can read the key or data at this moment.

An envelope encryption flow. The wrapping key never leaves the key store; the data key exists in plaintext only in memory, for as long as it is needed.

On Android, the wrapping key is an AES key generated in Keystore. The configuration below is standard platform code:

val spec = KeyGenParameterSpec.Builder(
    "local_data_wrapping_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setKeySize(256)
    .build()

KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    .apply { init(spec) }
    .generateKey()

On iOS, a token belongs in the Keychain with an accessibility class that matches when the app needs it, and files get an explicit protection class:

let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "refresh_token",
    kSecValueData as String: tokenData,
    kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)

try statement.write(to: fileURL, options: .completeFileProtection)

Rotation and recovery

Two lifecycle events break naive designs. Rotation: when a wrapping key is replaced, the app re-wraps the data keys under the new key rather than re-encrypting every file. Recovery: this-device-only Keychain items and Keystore keys do not travel in backups or device transfers, so a restored app finds encrypted files and no key. Plan for that on day one. The right behaviour is to detect the missing key, discard the unreadable local data and fetch it again after the user signs in.

Runtime scope: where encryption stops

Encryption at rest answers one threat: someone reading storage without the app. It does not answer an attacker who runs the app, or who controls the device while the app is running.

Encryption at rest helps against

  • Files copied from a lost or seized device
  • Backups and device-transfer copies of app data
  • Raw file reads on a rooted or jailbroken device, while keys stay in the key store
  • Forensic extraction of an app's sandbox

Needs other controls

  • Hooking frameworks reading data after decryption
  • Debuggers and memory dumps during a session
  • A repackaged app that calls the decryption code itself
  • Screenshots and screen recording of decrypted content
Encryption and runtime protection answer different questions, so evaluate them together.

This is where Otukan’s code and data protection fits. Otukan encrypts the files, databases and preferences your app marks as sensitive, with keys generated in Android Keystore or held in the iOS Keychain as this-device-only items. Before protected data is decrypted into memory, Otukan runs hooking and debugger checks and can refuse decryption under the policy you set. It keeps secrets out of the app package and obfuscates the code that handles keys, so the plaintext window is shorter and harder to reach. For secrets shipped with the app, see mobile app secrets protection. Platform detail is covered on Android app data encryption and iOS app data encryption.

Ask any vendor the same question: at which moment is my data plaintext, and what is watching that moment?

Otukan application security engineering

Evaluation checklist

  • Every file and store in the sandbox is listed with its data, boundary and protection.
  • Items that do not need to be stored have been removed.
  • Keys are generated on the device, inside Android Keystore or protected by the Keychain and Secure Enclave, and never written to app storage.
  • Android and iOS behaviour is described and tested separately, per supported OS version.
  • Rotation re-wraps data keys without losing user data.
  • A restore to a new device is tested, and the app recovers cleanly without the old key.
  • Runtime checks run before sensitive data is decrypted.
  • Any validation or certification claim comes with its certificate and scope.

Common questions

  • Is platform storage encryption enough on its own?

    Not for sensitive items. Android and iOS encrypt user storage, which protects a locked or powered-off device. App-layer encryption decides what stays protected while the phone is unlocked and in use. On iOS, Data Protection also depends on the user having a passcode, which is another reason to encrypt sensitive items in the app.

  • Where does Otukan keep its encryption keys?

    On Android, keys are generated in Android Keystore. On iOS, they are held in the Keychain as this-device-only items. Otukan encrypts the files, databases and preferences your app marks as sensitive with those keys, keeps secrets out of the app package and obfuscates the code that handles keys.

  • Does the Secure Enclave encrypt app files?

    No. The Secure Enclave holds elliptic-curve keys that can sign or wrap a small symmetric key. Bulk data is encrypted with that symmetric key, outside the enclave. A common pattern is envelope encryption, where a non-exportable wrapping key protects a data key and the data key encrypts the files.

  • What happens to encrypted data after a backup restore?

    The key does not come with it. This-device-only Keychain items and Keystore keys do not travel in backups or device transfers, so a restored app finds encrypted files and no key. The right behaviour is to detect the missing key, discard the unreadable local data and fetch it again after the user signs in.

  • Does encryption at rest stop hooking or memory dumps?

    No. Encryption at rest answers someone reading storage without the app. Every encrypted value becomes plaintext in memory when the app uses it, so hooking, debuggers and repackaged apps need runtime controls. Otukan runs hooking and debugger checks before protected data is decrypted and can refuse decryption under the policy you set.

Keep reading

Keep local data yours

Tell us your platforms, frameworks and which data your app keeps on the device. We never ask for keys, binaries, credentials or production data.

Or request a technical demo