Protecting data on-device is non-negotiable. Phones are lost, rooted, jailbroken, or scraped by malicious apps. A sound local-data strategy layers hardware-backed keys, OS keychains, and encrypted stores, while minimizing what you keep at all.
What to Store (and What Not to)
- Store: short-lived access tokens, refresh tokens, device/session identifiers, minimal user profile, small feature flags.
- Avoid: raw PII, secrets for third-party services, long-term credentials, or irreversible personal data. If you must store sensitive fields, encrypt per field and prefer server fetch on demand.
Core Building Blocks
- Hardware-backed key storage
- iOS: Secure Enclave + Keychain. Use access controls (e.g., this device only, biometry required).
- Android: StrongBox/TEE via Android Keystore. Request hardwareBacked keys and set
setUserAuthenticationRequired(true)with a reasonable timeout.
- Keychain/Keystore as root of trust
Generate a random symmetric key (AES-GCM/ChaCha20-Poly1305). Wrap it (encrypt) with a hardware-backed asymmetric key pair and store the wrapped blob on disk. Rotate if compromise is suspected. - Encrypted databases and files
- SQLite: use SQLCipher or platform equivalents.
- Files: envelope encryption—one file key per file, wrapped by the master symmetric key.
- Attachments: chunk and encrypt individually; maintain per-chunk MACs.
- Biometric gates
Gate high-risk actions (viewing card numbers, exporting backups) behind Face ID/Touch ID on iOS; BiometricPrompt on Android. Avoid gating routine background sync.
Practical Patterns
- Token handling: Keep refresh tokens in Keychain/Keystore; store access tokens in memory only with short TTL. On app launch, re-mint access tokens via refresh where possible.
- Per-tenant/namespace separation: Namescope all secrets (e.g.,
com.app.prod.user.{id}.refresh). Prevent cross-account leakages on shared devices. - Backup policy: Mark highly sensitive keychain items non-migrating; prefer server re-auth after device restore.
- At-rest + in-use: Encrypt at rest, but also zeroize secrets in memory after use; avoid logging; scrub screenshots for sensitive screens (Android
FLAG_SECURE, iOSisScreenCapturedhandling).
Threat-Model Highlights
- Rooted/jailbroken devices: Detect and degrade gracefully—disable exports, require online re-verification, and show read-only views.
- Clipboard leakage: Never place secrets on the clipboard.
- Side-channel data: Strip EXIF, avoid storing raw images of IDs; store hashes or tokens instead.
- Replay and downgrade: Version encryption headers; include nonce/counter and algorithm identifiers; reject unknown versions.
Key Management and Rotation
- Version keys:
k_master_v1,k_master_v2… Store version alongside ciphertext; support dual-read during rotation. - Rotate triggers: app major release, suspected compromise, policy dates.
- Revocation: On logout, wipe local DB, file keys, and keychain entries; invalidate refresh tokens server-side.
Testing and Observability
- Unit tests: encrypt/decrypt golden vectors; simulate key loss and corrupt headers.
- Instrumentation: cold-boot flows, upgrade paths, and biometric failures (fallback PIN).
- Synthetic chaos: flip “hardware key unavailable,” “biometry lockout,” and cache corruption to confirm user-friendly recovery.
- Privacy-safe telemetry: count failures/successes; never log keys, nonces, or plaintext.
Compliance and UX
- Consent and transparency: Explain what is stored locally and why; provide an “Export/Delete my data” path.
- Least privilege: Grant file and media permissions only when needed; prefer scoped storage.
- UX guardrails: Cache decrypted data only for the current view; time out sensitive screens; show clear errors, not stack traces.
Bottom line: Treat on-device data as compromised by default. Anchor secrets in hardware, wrap everything with a short-lived symmetric key, encrypt databases and files, and gate risky views with biometrics. Keep the payload minimal, rotate keys predictably, and prove correctness with tests that simulate your worst days.




