android

Android Screen Security: When to Use FLAG_SECURE and Keystore

Most banking apps show a blank panel in the recent apps switcher. The screen that normally displays your balance disappears, replaced by a grey rectangle or the app icon. That behaviour is not an accident. It is a deliberate security choice, and the technical lever behind it sits inside the Android window system.

Two tools carry most of the weight when an Android app handles credentials, session tokens or other sensitive material: the FLAG_SECURE window flag, and the Android Keystore for on-device encryption. Used together, they cover two very different threat models. Used badly, they quietly break user experience.

What FLAG_SECURE actually does

Three things, and only three things. It blocks the system from grabbing snapshots for the recents thumbnail. It stops the user from taking manual screenshots. It blocks screen recording. Nothing else.

That sounds like a "set it once and forget it" switch. It is not. The flag is attached to the window, not to an individual composable. Leave it on after navigating to a non-sensitive screen and your share-sheet, support chat or VPN server picker will refuse to cooperate with the user. The reference demonstrates this with two Jetpack Compose helpers, `FlagSecureEnable` and `FlagSecureDisable`, each wrapped in a `SideEffect` so the flag toggles synchronously after composition, before pixels reach the screen.

Practical catch: every public screen must explicitly call `FlagSecureDisable` on entry, or the previous screen's lock will carry over.

What FLAG_SECURE does not do

It does nothing about a device that has already been rooted, a file system dump pulled over ADB, or a backup that gets restored onto an attacker-controlled phone. Once an adversary can read raw storage, FLAG_SECURE is irrelevant.

This is where the Android Keystore enters. The reference pairs the window flag with an `AppCrypto` object that generates a 256-bit AES key inside the `AndroidKeyStore` provider, marks it for encrypt and decrypt, and uses AES/GCM/NoPadding. A new IV is forced for every encryption call, which prevents the same plaintext plus same key from producing identical ciphertext.

The key never leaves the secure hardware enclave. Apps ask the keystore to encrypt or decrypt, and the hardware does the work. The reference snippet shows this implemented as a Kotlin object with `getOrCreateKey()` returning a `SecretKey` and `encrypt()` returning a base64 string on `Dispatchers.IO`.

Our analysis

The interesting design decision here is not the cryptography. AES-GCM with hardware-backed keys is a well-trodden path. The interesting decision is where to flip the window flag. Per-screen toggling trades a small amount of boilerplate for the right user experience on every destination.

The real cost of getting this wrong is asymmetric. A forgotten `FlagSecureDisable` on a support screen generates support tickets and frustrated users. A forgotten `FlagSecureEnable` on a login screen generates… Nothing visible. Users won't report it. Attackers notice. This asymmetry means the "unlock everything" bug is the silent one, and it only surfaces during a security audit or a penetration test.

For most consumer apps, the rule of thumb is simple. Lock the surfaces that show secrets: login screens, credential reveal flows, two-factor codes, payment confirmations. Leave the rest alone. The worst FLAG_SECURE failure mode is the one a tester finds late: a customer support screen where the user is asked to record a session for debugging, and the record button is silently dead.

Decision Checklist

Screen type FLAG_SECURE Keystore encrypt
Login / password entry ✅ Enable ❌ Not yet stored
Credential reveal ✅ Enable ✅ Encrypt before saving
Session token display ✅ Enable ✅ Encrypt at rest
VPN server list ❌ Disable ❌ Not sensitive
Settings ❌ Disable ❌ Not sensitive
Customer support ❌ Disable ❌ Not sensitive
Payment confirmation ✅ Enable ✅ Encrypt transaction data

What to watch next

If you maintain an Android app that handles anything private, audit your navigation graph this week. List every screen. Mark which ones must hide from the recents carousel and which must not. If the answer is "all of them, sometimes," your FLAG_SECURE logic is fragile and your users already know.

A worked example

Say you are building a password manager for Android. The vault list, the password generator and the settings screen are all fine to share, screenshot or record. The password reveal screen, the autofill prompt and the recovery phrase display are not. Following the reference pattern, you call `FlagSecureEnable` at the top of `RevealPasswordScreen` and `FlagSecureDisable` on the way into `GeneratorScreen`. The reveal screen also wraps the value in `AppCrypto.encrypt` before writing it to disk, so a stolen backup is useless without the device's secure hardware. Two layers, two different jobs, both required.

Key takeaways

  • FLAG_SECURE is a window-level flag and must be toggled per screen, not per app.
  • Pair it with Android Keystore encryption, since the flag does not protect on-disk data.
  • Use SideEffect for synchronous flag toggling in Compose, not LaunchedEffect.
  • The asymmetric failure mode (forgotten enable = silent security hole) makes code review discipline essential.

Frequently asked questions

Does FLAG_SECURE stop rooted devices from reading app data?

No. It only blocks screenshots, screen recording and the recent apps thumbnail. For at-rest protection of files, you still need Android Keystore encryption or equivalent.

Why is SideEffect used instead of LaunchedEffect in the Compose helpers?

SideEffect runs synchronously after each composition, so the flag is set before the screen is drawn. LaunchedEffect runs on a coroutine and can race with rendering.

Can I set FLAG_SECURE once for the whole activity?

Technically yes, but a window-level flag stays on for every screen in that window, so any screen that needs to be shareable or screenshot-friendly will break.

What encryption mode does the reference recommend?

AES/GCM/NoPadding with a 256-bit key stored in AndroidKeyStore and a fresh IV per encryption operation.

Where is the encryption key stored?

Inside the device's secure hardware enclave via the Android Keystore provider. The raw key material never leaves that hardware.

Related