Guide

How to store secrets in a mobile app without hardcoding them

You can't safely ship a real secret in a client app — anything in the binary is extractable. The right patterns: keep secrets server-side, use the platform keystore, and restrict any key that must ship.

Last updated 2026-09-07

Short answer: Treat the app as fully readable by an attacker, because it is — decompiling, obfuscation, native code, and "encrypted" strings don't hide a secret, they just slow it down. The real fixes: keep true secrets on a backend the app calls with per-user auth; store on-device sensitive data in the Android Keystore / iOS Keychain; and for keys that genuinely must ship (e.g. a Maps key), restrict them so a leaked copy is useless.

Read in context. These instructions need to be adapted to your app and platform version. New scans provide static detections; they do not establish current backend access. Current methodology.

Why a client can't hold a secret

Everything shipped in an APK/IPA travels to the user's device and can be recovered — from resources, the DEX, or native .so files. Obfuscation, string "encryption", and moving the value into the NDK all raise the effort slightly but don't change the outcome: a determined attacker (or an automated scanner) extracts it. So the goal isn't to hide the secret — it's to not ship one.

Pattern 1 — keep it server-side (the default)

Put the API call behind your own backend. The app authenticates the user (OAuth/OIDC with PKCE, or your session), your backend holds the third-party key and makes the call, and returns only what the user is allowed to see. The client never sees the key.

Pattern 2 — platform key stores for on-device data

For secrets that must live on the device (a derived token, encryption keys for local data), use the Android Keystore / iOS Keychain — hardware-backed where available, so the key material isn't extractable even from a rooted device. Never roll your own with a hardcoded key.

Pattern 3 — restrict keys that must ship

Some keys are designed to be public (a Google Maps key, a Firebase config). Make a leaked copy worthless: apply application + API restrictions (package name + signing cert + specific APIs) in the provider console, add Firebase App Check, and set spend/quota limits. The key ships, but only your app can use it.

What doesn't work

  • Obfuscation / string encryption — the app must decrypt at runtime, so the value is recoverable. Defense-in-depth at best, never the control.
  • Hiding in the NDK — native strings are just as extractable (often easier — a plain strings pass).
  • Split/encoded keys — reassembled at runtime, recovered at runtime.

Maps to CWE-798 and MASVS-STORAGE-1. If you already shipped one, find it and rotate immediately.

Frequently asked questions

Can't I just obfuscate or encrypt the key?

No — the app has to decrypt it to use it, so anyone running/decompiling the app recovers it. Obfuscation slows a casual look; it doesn't protect a secret from a scanner or a motivated attacker.

Is putting the key in native (NDK) code safer?

Marginally, and not meaningfully. Native binaries are readable; hardcoded strings/keys in a .so are extractable, often with a simple strings dump. It's not a secret store.

What about API keys that genuinely must be in the app?

Restrict them so a leaked copy is useless: lock to your package + signing cert + specific APIs, add App Check/attestation, and cap quota. The key can be public as long as only your app can successfully use it.