Most Android app compromises come from settings the developer turned off or never set: an exported component, a debug trust anchor in a release build, a token in plain SharedPreferences, or an API key compiled into the APK. Android gives app teams strong primitives (Keystore with TEE and StrongBox, Network Security Configuration, Play Integrity, Credential Manager), but each has defaults, version cut-offs and server-side duties that are easy to get wrong. This guide is the Android-specific companion to the cross-platform Mobile App Security Guide: it gives the manifest attributes, API names, API levels and verification steps an Android engineer needs, plus a rollout plan, metrics and failure patterns. It is written for Android developers, mobile security engineers and the application security teams who set release gates for them. For test methods see the Mobile App Security Testing Guide.
Key takeaways
- Keys belong in Android Keystore, not in files. Keystore key material never enters the app process, and it can be bound to the TEE or StrongBox. The Jetpack security-crypto library (EncryptedSharedPreferences) was deprecated in April 2025 in favour of direct Keystore use.
- Declare every component's exposure. Since Android 12 (API 31) an activity, service or receiver with an intent filter must declare
android:exportedor the app will not install. Default tofalseand protect the rest withsignaturepermissions. - Network Security Configuration is the TLS policy file. Cleartext is off by default from API 28, debug trust anchors only apply when the build is debuggable, and pins need a backup pin and a planned expiry.
- Play Integrity is a server-side signal. SafetyNet Attestation was turned down in January 2025. Decide on the verdict on your backend, bind it to the request, and never trust a check the app performs on itself.
- The APK is public. Anything compiled in, including API keys and cloud credentials, can be extracted. Use attestation plus a backend token exchange so the app holds only short-lived, device-bound credentials.
The Android security model in practice
Android runs each app under its own Linux UID in a sandbox, so files in internal storage are private to the app by default. Android's security tips describe this default as sufficient for most applications. Everything outside the sandbox is a decision the developer makes: what to export over IPC, what to put in external storage or backups, which certificate authorities to trust, and what to log. The platform has also tightened defaults over time, so the targetSdkVersion you build against changes your security posture.
| API level | Change that matters for security | What to check |
|---|---|---|
| 21 (Android 5.0) | Implicit intents passed to bindService() throw an exception | All service binding uses explicit intents |
| 24 to 27 | Default Network Security Configuration trusts only system CAs (user-added CAs were trusted by default only up to API 23) | No <certificates src="user"/> in release config |
| 28 (Android 9) | Cleartext traffic disabled by default; StrongBox KeyMint available on supporting devices; end-to-end encrypted backup when a lock screen is set | No cleartextTrafficPermitted="true" in base-config |
| 29 (Android 10) | KeyInfo.getSecurityLevel() reports TRUSTED_ENVIRONMENT or STRONGBOX | Keys report a hardware security level |
| 31 (Android 12) | Explicit android:exported required for components with intent filters; PendingIntent mutability must be declared; dataExtractionRules replace fullBackupContent; adb backup excludes app data | Lint clean for UnspecifiedImmutableFlag; both backup rule files present |
| Android 16 launch devices | Key attestation uses Remote Key Provisioning only, phasing out factory keys | Server attestation verifier handles RKP chains |
Storing data and keys: Keystore, TEE and StrongBox
The Android Keystore system performs cryptographic operations in a system process, so an attacker who compromises the app process can use its keys but cannot extract them. Where the device's secure hardware supports the algorithm, key material is bound to the Trusted Execution Environment (TEE) or a Secure Element and never leaves it.
- Generate keys in Keystore: use
KeyPairGeneratororKeyGeneratorwith the"AndroidKeyStore"provider and aKeyGenParameterSpecthat fixes purposes, digests, padding and block modes. These authorisations cannot be changed after creation. - Check where the key lives: on API 29 and higher,
KeyInfo.getSecurityLevel()should returnTRUSTED_ENVIRONMENTorSTRONGBOX; on API 28 and lower useisInsideSecurityHardware(). - Use StrongBox selectively: check
FEATURE_STRONGBOX_KEYSTORE, callsetIsStrongBoxBacked(true), and catchStrongBoxUnavailableExceptionto fall back to the TEE. StrongBox supports RSA 2048, AES 128 and 256, ECDSA and ECDH on P-256, HMAC-SHA256 and Triple DES. Android's own documentation says it is slower and not necessary for most apps; reserve it for high-value signing keys. - Bind keys to the user:
setUserAuthenticationParameters()supports a time-based window or per-operation authorisation withAUTH_BIOMETRIC_STRONG,AUTH_DEVICE_CREDENTIALor both, triggered throughBiometricPrompt. Biometric-only keys are invalidated when a new biometric is enrolled unless you passfalsetosetInvalidatedByBiometricEnrollment(); for banking-grade apps keep the default. - Plan the EncryptedSharedPreferences migration:
androidx.security:security-cryptodeprecated all APIs in 1.1.0-alpha07 (9 April 2025) in favour of platform APIs and direct use of Android Keystore. Existing data should be decrypted and re-encrypted with a Keystore-held AES-GCM key in a controlled app update. - Keep data out of shared places: never use the deprecated
MODE_WORLD_READABLEorMODE_WORLD_WRITEABLE, keep sensitive data out of external storage, and verify any code loaded from external storage cryptographically before use.
val spec = KeyGenParameterSpec.Builder("session-signing",
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(serverNonce) // server-issued, single use
.setIsStrongBoxBacked(hasStrongBox) // fall back on StrongBoxUnavailableException
.build()
Backups and data extraction
Auto Backup is on by default. For apps targeting API 31 and higher, set android:dataExtractionRules on <application> with separate <cloud-backup> and <device-transfer> sections; if device transfer rules are absent, all app data moves during device-to-device migration. Keep a fullBackupContent file as well for devices on Android 11 and lower. Exclude token stores and databases holding personal data, or require encryption with disableIfNoEncryptionCapabilities="true" (the Android 12 name for clientSideEncryption). Android's backup guidance names home-grown "export" features that write app data to shared directories as the main risk.
Exported components, intents and WebViews
IPC is where Android apps most often expose functionality to other apps on the same device. Treat every exported component as a public API.
| Component or API | Risk | Safe setting |
|---|---|---|
| Activity, service, receiver with intent filter | Other apps can launch it or send it data | android:exported="false" unless it is the launcher or a deliberate entry point (required to be explicit from API 31) |
| ContentProvider | Other apps read or write the app's data | android:exported="false"; if shared with your own apps, a permission with android:protectionLevel="signature" |
| Service IPC calls | Caller lacks the right to the operation | Declarative manifest permissions, or checkCallingPermission() in the handler |
| Broadcasts | Any app receives sensitive broadcasts | Explicit intents, or sendBroadcast with a non-null receiver permission |
| PendingIntent | A receiving app modifies the wrapped intent | Declare mutability (FLAG_IMMUTABLE by default) and fix the UnspecifiedImmutableFlag lint warning |
| WebView | Untrusted script calls native code through a bridge | Leave JavaScript off unless needed; use addJavascriptInterface() only for fully trusted, ideally bundled, content; clearCache() after sensitive sessions |
| Logs | Apps holding READ_LOGS read PII and tokens | No PII or tokens in logs; strip verbose logging from release builds |
- Validate everything received: intent filters are not a security boundary. Validate intent extras, deep link parameters and provider queries as untrusted input, and use the Android 12 StrictMode check for unsafe intent launches during testing.
- Use explicit intents internally: name the target component for anything sensitive, and keep internal-only IPC unexported even in multi-process apps that share a UID.
Network Security Configuration and TLS
The android:networkSecurityConfig attribute on <application> points to an XML file, usually res/xml/network_security_config.xml. It holds one optional <base-config>, any number of <domain-config> elements (the longest matching domain rule wins) and one optional <debug-overrides>.
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors><certificates src="system"/></trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-06-30">
<pin digest="SHA-256">[primary SPKI hash]</pin>
<pin digest="SHA-256">[backup SPKI hash]</pin>
</pin-set>
</domain-config>
<debug-overrides>
<trust-anchors><certificates src="@raw/debug_cas"/></trust-anchors>
</debug-overrides>
</network-security-config>
- Test CAs go in debug-overrides only: the section is ignored when
android:debuggableis false, so an interception proxy CA cannot leak into release. Never addsrc="user"to a release base-config. - Pins need a backup and an expiry policy: pin digests are SHA-256 hashes of the SubjectPublicKeyInfo. Ship at least two. After the
expirationdate pinning is disabled, which avoids bricking old installs but also lets an attacker with a trusted CA bypass pins on stale versions; pair expiry with a forced-update policy. - Do not hand-roll TLS: use
HttpsURLConnectionor a maintained client. Android's security tips warn that rawSSLSocketdoes not verify hostnames, and customTrustManagerorHostnameVerifierclasses that accept everything remain a top test finding.
Play Integrity and key attestation
Play Integrity replaced SafetyNet Attestation, which Google deprecated in 2022 and fully turned down in January 2025; the old attest call now always fails. Play Integrity returns an encrypted verdict that your server must decode and check.
| Verdict field | Expected value | What it tells you |
|---|---|---|
requestDetails | Matching requestHash (standard) or nonce (classic) | The verdict belongs to this request and is not a replay |
appIntegrity.appRecognitionVerdict | PLAY_RECOGNIZED | Signing certificate recognised by Play, binary untampered |
accountDetails.appLicensingVerdict | LICENSED | User installed or updated the app from Google Play |
deviceIntegrity labels | MEETS_STRONG_INTEGRITY, MEETS_DEVICE_INTEGRITY, MEETS_BASIC_INTEGRITY | Tiered device trust; strong requires hardware-backed signals and a recent security patch on Android 13 and higher |
Optional: appAccessRiskVerdict, playProtectVerdict, recentDeviceActivity | Policy dependent | Capture or overlay apps, known malware, request bursts from one device |
- Choose the request type: standard requests need a warm-up, return in a few hundred milliseconds and bind the verdict with
requestHash; classic requests take seconds and use a servernonce. Use standard requests for frequent checks and classic for rare high-value actions. Google advises against caching verdicts. - Grade the response: the documentation recommends a range of outcomes (allow, allow with limits, allow after challenge, deny) rather than a binary block, and handling failures with the remediation dialog.
- Watch the quota: the default is 10,000 requests per day across all installs, so call it at sign-in, enrolment and sensitive transactions rather than on every API call.
- Use key attestation for device-bound keys: generate a Keystore key with
setAttestationChallenge(), send the certificate chain to your server, verify it chains to a Google attestation root (published at android.googleapis.com/attestation/root, with a new root signing from 1 February 2026), checkattestationSecurityLeveland the revocation list, and read only the first attestation extension nearest the root. Google recommends its key attestation verification library over custom parsers, and never validating on the device itself.
Where identity fits: API keys, tokens and the app as a client
An Android app is a non-human client with no way to keep a secret. Symantec's research on mobile apps with hard-coded AWS keys found 1,859 Android and iOS apps carrying cloud credentials, 53% of them reusing tokens that came from shared libraries and SDKs, so one vendor's mistake was copied into many apps.
- Ship no backend secrets: cloud keys, payment secret keys and AI provider keys stay on your servers in a vault. See the Secrets Management Guide.
- Attest, then exchange: the app sends a Play Integrity verdict (and optionally a key attestation chain) to your backend, which issues a short-lived access token bound to a Keystore key held on the device. Calls are signed with that key, so a copied token is useless elsewhere.
- Restrict unavoidable client keys: mapping or push keys that must ship should be restricted by package name and signing certificate fingerprint and limited to the APIs they need. See the API Key Management Guide.
- Sign users in with Credential Manager: the Jetpack
androidx.credentialsAPI unifies passkeys, passwords and Sign in with Google, and replaces the legacy Google Sign-In, One Tap and Smart Lock for Passwords APIs. See the Passwordless and Passkeys Guide. - Protect the signing identity: the upload and app signing keys, and the CI account that publishes to Play, can push code to every user. Keep them in a managed signing service or HSM with named owners. See the Code Signing Security Guide.
Developer verification
Google is adding developer identity verification for apps installed on certified Android devices. Its developer verification page lists APIs, limited distribution accounts and an advanced flow for power users from August 2026, enforcement from 30 September 2026 for installs on Android 7 and higher in Brazil, Indonesia, Singapore and Thailand, and global expansion in 2027. Google Play registers most Play apps automatically; teams distributing outside Play register through the Android Developer Console. Check the current page before planning, as the timeline has been updated during 2026.
Choosing controls by app risk
| Control | Low-risk app (content, utilities) | Regulated or high-value app (banking, health, enterprise) |
|---|---|---|
| Key storage | Keystore, TEE | Keystore with user authentication; StrongBox for signing keys where present |
| Integrity | Play Integrity at sign-in, MEETS_DEVICE_INTEGRITY as a soft signal | Standard requests on sensitive actions, MEETS_STRONG_INTEGRITY for high-value transactions, key attestation for device binding |
| TLS | Default config, cleartext off | Pinning with backup pins and forced update |
| Backups | Exclude tokens | Exclude tokens and personal data, or require end-to-end encryption |
| Screens | None | FLAG_SECURE on sensitive screens, review of appAccessRiskVerdict |
| Verification | OWASP MASVS L1 style baseline | MASVS with the resilience group and annual independent MASTG testing |
Small teams get most value from four things: target the current API level, keep exported components off, store tokens in Keystore-backed storage, and remove every embedded secret. Large organisations with many apps should centralise the attestation verifier and token service so each app team does not build its own.
Implementation roadmap
- Inventory and baseline: list apps,
targetSdkVersion, SDKs and every secret found by scanning the built APK or AAB, not just source. - Remove secrets first: move backend keys behind your API, rotate every key that has ever shipped, and restrict the client keys that remain.
- Fix the manifest: explicit
exportedvalues, signature permissions,debuggablefalse, backup rules and a release Network Security Configuration. Make Android Lint security checks blocking in CI. - Migrate storage: move tokens to Keystore-backed encryption and plan the security-crypto exit in one release with a fallback read path.
- Integrity in observe mode: call Play Integrity, log verdicts server-side for several weeks without blocking, and measure how many genuine users fail each tier.
- Enforce in rings: start with high-value actions and a small percentage of traffic, using graded outcomes. Roll back by switching the server policy to observe, which needs no app release.
- Gate releases: MASTG-based tests and a secrets scan on every release build, with a minimum supported version so old, weaker builds can be retired.
Detection and measurement
- Log server-side: integrity verdict distribution per app version, attestation failures, token exchange failures, and calls arriving without a valid device-bound token.
- Watch embedded keys: usage of any client key by origin, and alerts for use outside expected package and certificate restrictions.
| Metric | Target |
|---|---|
| Hard-coded secrets found in release builds | Zero; any finding blocks release |
| Share of sensitive API calls backed by a verified integrity verdict | Above 95% for high-value apps |
| Exported components without a documented reason | Zero |
| Active sessions on versions below the minimum supported version | Falling each quarter, under 5% |
| Time to rotate a leaked client key end to end | Under 24 hours |
Framework mapping
| Area | OWASP MASVS group | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | MITRE ATT&CK |
|---|---|---|---|---|
| Keystore and data storage | MASVS-STORAGE, MASVS-CRYPTO | SC-12, SC-28 | PR.DS-01 | T1409 Stored Application Data (Mobile) |
| Network Security Configuration | MASVS-NETWORK | SC-8 | PR.DS-02 | T1638 Adversary-in-the-Middle (Mobile) |
| Exported components and WebViews | MASVS-PLATFORM | AC-6 | PR.PS-06 | n/a |
| Embedded secrets and token exchange | MASVS-AUTH, MASVS-STORAGE | IA-5 | PR.AA-01 | T1552.001 Credentials In Files (Enterprise) |
| Play Integrity and attestation | MASVS-RESILIENCE | SI-7 | PR.PS-06 | n/a |
| Release testing | MASVS-CODE | SA-11 | ID.RA-01 | n/a |
The mapping is NHI Mgmt Group's own. For the risk view, see the OWASP Mobile Top 10 Guide.
Common failure patterns
- Cloud and AI keys in the APK: keys in
BuildConfig, resources or SDK config files are extracted with standard decompilers. Moving them to native code or obfuscating them only slows extraction. - Debug settings in release:
android:debuggable="true", asrc="user"trust anchor in base-config, or cleartext enabled to make a test server work. - Pins without a backup: a certificate rotation breaks every installed copy, and the emergency fix is usually to remove pinning entirely.
- Integrity checked on the device: the app reads its own verdict and decides, or the server never checks
requestHash, so a hooked app replays a good verdict. - Exported by accident: an intent filter added for a feature makes a service or activity reachable by every app, and the handler trusts its extras.
- Secrets in backups and logs: tokens in SharedPreferences included in cloud backup or device transfer, and tokens printed in Logcat during debugging that stay in release.
Lessons from incidents
The Symantec hard-coded AWS keys research showed that 77% of affected apps held valid AWS tokens and 47% held tokens with full access to private files in Amazon S3. The lesson for Android teams is that secrets often arrive through third-party SDKs, so scanning has to cover the compiled package and the SDK inventory, and key rotation has to be possible without breaking installed versions.
Practitioner checklist
- Target the current API level and make Android Lint security warnings blocking in CI.
- Scan every release APK or AAB for secrets and rotate any key that has ever shipped.
- Store keys in Android Keystore, confirm a hardware security level, and plan the security-crypto migration.
- Set
android:exportedexplicitly on every component and usesignaturepermissions for shared IPC. - Ship a Network Security Configuration with cleartext off, debug CAs only in
debug-overrides, and backup pins. - Verify Play Integrity verdicts on the server, bound to the request, with graded outcomes.
- Exchange attestation for short-lived, device-bound tokens; restrict any client key by package and certificate.
- Define
dataExtractionRulesandfullBackupContentthat exclude tokens and personal data. - Adopt Credential Manager for passkeys and retire legacy sign-in APIs.
- Check Google's developer verification timeline for every distribution channel you use.
Standards and references
- Android Developers: Security tips
- Android Developers: Android Keystore system
- Android Developers: Network security configuration
- Android Developers: Play Integrity API overview
- Android Developers: Verify hardware-backed key pairs with key attestation
- Android Developers: Behaviour changes for apps targeting Android 12
- Android Developers: Android developer verification
- OWASP: Mobile Application Security Verification Standard
Related NHI Mgmt Group resources: Mobile App Security Guide · iOS App Security Guide · Mobile App Security Testing Guide · Android Enterprise Security Guide · Mobile Malware Guide