{"id":100004,"date":"2026-10-19T15:22:51","date_gmt":"2026-10-19T15:22:51","guid":{"rendered":"http:\/\/nandee-post-2"},"modified":"2026-10-19T15:22:51","modified_gmt":"2026-10-19T15:22:51","slug":"android-app-security","status":"publish","type":"post","link":"https:\/\/www.nandee.ai\/blog\/android-app-security\/","title":{"rendered":"Android App Security: The Developer\u2019s Guide"},"content":{"rendered":"<p>Android gives developers a strong security foundation: app sandboxing, a permission model, hardware-backed key storage, and verified app links. Most real-world Android vulnerabilities do not come from the platform. They come from how an app uses it, such as a component exported by accident, a token stored in plain text, or a WebView that loads more than it should.<\/p>\n<p>This guide walks through the areas that matter most when securing an Android app, in the order most teams should tackle them. Each section explains the risk, the fix, and what to check before release.<\/p>\n<p><strong>Quick answer:<\/strong> To secure an Android app, keep secrets on your server, store tokens with Android Keystore, block cleartext traffic, lock down the manifest (no unneeded exported components, debugging off, backups restricted), validate every intent and deep link, restrict WebViews, keep dependencies updated, and verify app integrity on your server with the Play Integrity API.<\/p>\n<h2>1. Secrets and credentials<\/h2>\n<p>Anything inside your APK or AAB can be read by anyone who downloads it. That includes BuildConfig fields, strings.xml, assets, and native libraries.<\/p>\n<p>\u2022Keep third-party secret keys on your backend and have the app call your backend instead.<\/p>\n<p>\u2022Issue short-lived, narrowly scoped tokens to the app.<\/p>\n<p>\u2022Restrict any key that must ship (such as a maps key) to your package name and signing certificate in the provider\u2019s console.<\/p>\n<p>This is OWASP\u2019s number one mobile risk. See <u>M1 Improper Credential Usage<\/u> for patterns and fixes.<\/p>\n<h2>2. Secure data storage<\/h2>\n<p>Each app has a private storage area, but that is not enough for sensitive data. Backups, rooted devices, and debugging tools can expose it.<\/p>\n<p>\u2022<strong>Store as little as possible.<\/strong> If the app does not need it offline, keep it on the server.<\/p>\n<p>\u2022<strong>Encrypt sensitive values with a Keystore key.<\/strong> The Android Keystore holds keys in secure hardware on most devices, so the key material is never exposed to the app process.<\/p>\n<p><span style=\"color: rgb(0, 112, 32)\"><strong>val<\/strong><\/span> <span style=\"color: rgb(25, 23, 124)\">spec<\/span> <span style=\"color: rgb(102, 102, 102)\">=<\/span> KeyGenParameterSpec<span style=\"color: rgb(102, 102, 102)\">.<\/span>Builder<span style=\"color: rgb(102, 102, 102)\">(<\/span><br \/>&nbsp;&nbsp;&nbsp; <span style=\"color: rgb(64, 112, 160)\">&#8220;session_key&#8221;<\/span><span style=\"color: rgb(102, 102, 102)\">,<\/span><br \/>&nbsp;&nbsp;&nbsp; KeyProperties<span style=\"color: rgb(102, 102, 102)\">.<\/span>PURPOSE_ENCRYPT or KeyProperties<span style=\"color: rgb(102, 102, 102)\">.<\/span>PURPOSE_DECRYPT<br \/><span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/>&nbsp;&nbsp;&nbsp; <span style=\"color: rgb(102, 102, 102)\">.<\/span>setBlockModes<span style=\"color: rgb(102, 102, 102)\">(<\/span>KeyProperties<span style=\"color: rgb(102, 102, 102)\">.<\/span>BLOCK_MODE_GCM<span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/>&nbsp;&nbsp;&nbsp; <span style=\"color: rgb(102, 102, 102)\">.<\/span>setEncryptionPaddings<span style=\"color: rgb(102, 102, 102)\">(<\/span>KeyProperties<span style=\"color: rgb(102, 102, 102)\">.<\/span>ENCRYPTION_PADDING_NONE<span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/>&nbsp;&nbsp;&nbsp; <span style=\"color: rgb(102, 102, 102)\">.<\/span>setKeySize<span style=\"color: rgb(102, 102, 102)\">(<\/span><span style=\"color: rgb(64, 160, 112)\">256<\/span><span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/>&nbsp;&nbsp;&nbsp; <span style=\"color: rgb(102, 102, 102)\">.<\/span>build<span style=\"color: rgb(102, 102, 102)\">()<\/span><\/p>\n<p><span style=\"color: rgb(0, 112, 32)\"><strong>val<\/strong><\/span> <span style=\"color: rgb(25, 23, 124)\">keyGen<\/span> <span style=\"color: rgb(102, 102, 102)\">=<\/span> KeyGenerator<span style=\"color: rgb(102, 102, 102)\">.<\/span>getInstance<span style=\"color: rgb(102, 102, 102)\">(<\/span>KeyProperties<span style=\"color: rgb(102, 102, 102)\">.<\/span>KEY_ALGORITHM_AES<span style=\"color: rgb(102, 102, 102)\">,<\/span> <span style=\"color: rgb(64, 112, 160)\">&#8220;AndroidKeyStore&#8221;<\/span><span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/>keyGen<span style=\"color: rgb(102, 102, 102)\">.<\/span>init<span style=\"color: rgb(102, 102, 102)\">(<\/span>spec<span style=\"color: rgb(102, 102, 102)\">)<\/span><br \/><span style=\"color: rgb(0, 112, 32)\"><strong>val<\/strong><\/span> <span style=\"color: rgb(25, 23, 124)\">key<\/span> <span style=\"color: rgb(102, 102, 102)\">=<\/span> keyGen<span style=\"color: rgb(102, 102, 102)\">.<\/span>generateKey<span style=\"color: rgb(102, 102, 102)\">()<\/span><\/p>\n<p>\u2022<strong>Never write tokens or personal data to external storage, logs, or the clipboard.<\/strong><\/p>\n<p>\u2022<strong>Hide sensitive screens from screenshots and the recent apps view<\/strong> with FLAG_SECURE where it makes sense, such as payment or health screens.<\/p>\n<h2>3. Network security<\/h2>\n<p>Every request your app makes should be encrypted and validated.<\/p>\n<p>\u2022<strong>Block cleartext traffic<\/strong> with a network security config.<\/p>\n<p>\u2022<strong>Never override certificate validation<\/strong>, even temporarily for testing. Custom TrustManager or HostnameVerifier code that accepts everything is one of the most common findings in Android apps.<\/p>\n<p>\u2022<strong>Pin public keys for high-risk APIs<\/strong> and always include a backup pin so a certificate rotation does not break the app.<\/p>\n<p><span style=\"color: rgb(96, 160, 176)\"><em>&lt;!&#8211; res\/xml\/network_security_config.xml &#8211;&gt;<\/em><\/span><br \/>&lt;<span style=\"color: rgb(0, 112, 32)\"><strong>network-security-config<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>base-config<\/strong> cleartextTrafficPermitted=<\/span><span style=\"color: rgb(64, 112, 160)\">&#8220;false&#8221;<\/span> \/&gt;<br \/>&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>domain-config<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>domain<\/strong> includeSubdomains=<\/span><span style=\"color: rgb(64, 112, 160)\">&#8220;true&#8221;<\/span>&gt;<a target=\"_blank\" href=\"http:\/\/api.example.com\" rel=\"noopener noreferrer\">api.example.com<\/a>&lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>domain<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>pin-set<\/strong> expiration=<\/span><span style=\"color: rgb(64, 112, 160)\">&#8220;2027-01-01&#8221;<\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>pin<\/strong> digest=<\/span><span style=\"color: rgb(64, 112, 160)\">&#8220;SHA-256&#8221;<\/span>&gt;PRIMARY_KEY_HASH_BASE64=&lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>pin<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;<span style=\"color: rgb(0, 112, 32)\"><strong>pin<\/strong> digest=<\/span><span style=\"color: rgb(64, 112, 160)\">&#8220;SHA-256&#8221;<\/span>&gt;BACKUP_KEY_HASH_BASE64=&lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>pin<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>pin-set<\/strong><\/span>&gt;<br \/>&nbsp;&nbsp;&nbsp; &lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>domain-config<\/strong><\/span>&gt;<br \/>&lt;\/<span style=\"color: rgb(0, 112, 32)\"><strong>network-security-config<\/strong><\/span>&gt;<\/p>\n<p>Reference it in the manifest with android:networkSecurityConfig=&#8221;@xml\/network_security_config&#8221;.<\/p>\n<h2>4. Manifest hardening<\/h2>\n<p>The AndroidManifest.xml defines your app\u2019s attack surface. Review the merged manifest (including what libraries add) before every release.<\/p>\n<p>Setting<\/p>\n<p>Secure default<\/p>\n<p>Why it matters<\/p>\n<p>android:debuggable<\/p>\n<p>Never true in release<\/p>\n<p>A debuggable build lets anyone with USB access inspect and modify the running app<\/p>\n<p>android:allowBackup<\/p>\n<p>false, or use backup rules that exclude sensitive files<\/p>\n<p>Prevents app data from being copied out through backups<\/p>\n<p>android:exported<\/p>\n<p>false unless another app truly needs access<\/p>\n<p>Exported components can be started by any app on the device<\/p>\n<p>android:usesCleartextTraffic<\/p>\n<p>false<\/p>\n<p>Blocks unencrypted HTTP<\/p>\n<p>Permissions<\/p>\n<p>Only what the feature needs<\/p>\n<p>Fewer permissions mean less to abuse and better user trust<\/p>\n<p>Since Android 12, any component with an intent filter must declare android:exported explicitly. Treat that as a prompt to decide each one on purpose.<\/p>\n<h2>5. Components and intents<\/h2>\n<p>Activities, services, broadcast receivers, and content providers are entry points into your app.<\/p>\n<p>\u2022<strong>Keep components private<\/strong> unless they must be reachable by other apps.<\/p>\n<p>\u2022<strong>Protect exported components<\/strong> with a signature-level custom permission when only your own apps need access.<\/p>\n<p>\u2022<strong>Treat every incoming intent as untrusted input.<\/strong> Validate extras before using them, and never forward an incoming intent directly to startActivity without checks.<\/p>\n<p>\u2022<strong>Use explicit intents<\/strong> to start your own components, and set a package on implicit intents that carry sensitive data.<\/p>\n<p>\u2022<strong>Use PendingIntent.FLAG_IMMUTABLE<\/strong> unless the intent must be modified by the receiver.<\/p>\n<p>\u2022<strong>Use parameterized queries<\/strong> in content providers and restrict them with permissions.<\/p>\n<h2>6. WebViews<\/h2>\n<p>WebViews bring the web\u2019s risks into your app.<\/p>\n<p>\u2022Load only your own trusted origins, and block navigation to anything else.<\/p>\n<p>\u2022Keep JavaScript disabled unless the page needs it.<\/p>\n<p>\u2022Avoid addJavascriptInterface on any WebView that can load untrusted content.<\/p>\n<p>\u2022Disable file access (setAllowFileAccess(false)) unless you rely on it.<\/p>\n<p>\u2022Never ignore SSL errors in onReceivedSslError.<\/p>\n<h2>7. Deep links and App Links<\/h2>\n<p>Custom URL schemes can be claimed by any app, so they are not a safe way to deliver sensitive data like login codes.<\/p>\n<p>\u2022Use <strong>verified Android App Links<\/strong> (https links with android:autoVerify=&#8221;true&#8221; and a matching assetlinks.json on your domain) for anything sensitive.<\/p>\n<p>\u2022Validate every parameter that arrives through a link before acting on it.<\/p>\n<p>\u2022Never let a link parameter decide which URL a WebView loads without an allowlist.<\/p>\n<h2>8. Dependencies and SDKs<\/h2>\n<p>Third-party code runs with your app\u2019s permissions and access to its data.<\/p>\n<p>\u2022Pin dependency versions and review updates.<\/p>\n<p>\u2022Check what each SDK collects and which permissions it adds to the merged manifest.<\/p>\n<p>\u2022Generate a software bill of materials (SBOM) for each release.<\/p>\n<p>\u2022Keep your Google Play Data safety form consistent with what your SDKs actually do.<\/p>\n<h2>9. App integrity and server-side verification<\/h2>\n<p>Checks that run only on the device can be modified by someone who controls the device. For sensitive actions, let your server decide.<\/p>\n<p>The <a target=\"_blank\" href=\"https:\/\/developer.android.com\/google\/play\/integrity\/overview\" rel=\"noopener noreferrer\"><u>Play Integrity API<\/u><\/a> lets your server verify that a request comes from your genuine app binary, installed through Google Play, on a genuine certified Android device. Use it to protect high-value actions such as sign-up, payments, and promotions, and decide on the server how to respond to a failed verdict.<\/p>\n<h2>10. Release builds<\/h2>\n<p>\u2022Enable R8 code shrinking and obfuscation for release builds. It reduces what is exposed, though it does not protect secrets.<\/p>\n<p>\u2022Strip debug logging from release builds.<\/p>\n<p>\u2022Sign with a key stored securely (Play App Signing or a hardware-backed key store) and restrict who can access it.<\/p>\n<p>\u2022Set minSdkVersion as high as your users allow so you benefit from newer platform protections.<\/p>\n<h2>Android security release checklist<\/h2>\n<p>\u2610 No secret keys in code, resources, or build config<\/p>\n<p>\u2610 Sensitive data encrypted with a Keystore key, or not stored at all<\/p>\n<p>\u2610 Cleartext traffic blocked; no custom trust-all certificate code<\/p>\n<p>\u2610 Pinning with backup pins for high-risk APIs<\/p>\n<p>\u2610 debuggable off, backups restricted, each exported reviewed<\/p>\n<p>\u2610 Incoming intents and deep link parameters validated<\/p>\n<p>\u2610 WebViews restricted to trusted origins<\/p>\n<p>\u2610 Verified App Links for sensitive flows<\/p>\n<p>\u2610 Dependencies pinned, SBOM generated, Data safety form accurate<\/p>\n<p>\u2610 High-value actions verified server-side with Play Integrity<\/p>\n<p>\u2610 R8 enabled and debug logs stripped in release<\/p>\n<h2>FAQ<\/h2>\n<p><strong>What is the most common Android app security issue?<\/strong> Hardcoded secrets and insecure storage of tokens are among the most common, along with components exported without protection.<\/p>\n<p><strong>Is the Android app sandbox enough to protect user data?<\/strong> It is a strong baseline, but it does not protect against backups, rooted devices, or your own app\u2019s mistakes. Encrypt sensitive data with a Keystore key and store as little as possible.<\/p>\n<p><strong>Does R8 obfuscation make my app secure?<\/strong> No.&nbsp;R8 makes code harder to read and smaller, but it does not hide string values like API keys. Use it as one layer, not as protection for secrets.<\/p>\n<p><strong>Should every Android app use certificate pinning?<\/strong> Not every app. Pinning helps most for apps handling payments, health data, or authentication. Always include a backup pin and an expiration plan.<\/p>\n<p><strong>How does Play Integrity help security?<\/strong> It lets your server confirm that a request comes from your genuine app on a genuine device, so you can protect sensitive actions from modified apps and emulators.<\/p>\n<h2>Test your Android app<\/h2>\n<p><span style=\"color: rgb(0, 0, 0)\">Nandee is an AI-assisted mobile application security testing platform for Android and iOS apps. It checks your build against the issues in this guide and gives your developers prioritized fixes.<\/span><\/p>\n<h2>Sources<\/h2>\n<p>\u2022<a target=\"_blank\" href=\"https:\/\/developer.android.com\/google\/play\/integrity\/overview\" rel=\"noopener noreferrer\"><u>Android Developers: Play Integrity API overview<\/u><\/a><\/p>\n<p>\u2022<a target=\"_blank\" href=\"https:\/\/mas.owasp.org\/MASVS\/\" rel=\"noopener noreferrer\"><u>OWASP MASVS<\/u><\/a><\/p>\n<p>\u2022<a target=\"_blank\" href=\"https:\/\/owasp.github.io\/www-project-mobile-top-10\/2023-risks\/\" rel=\"noopener noreferrer\"><u>OWASP Mobile Top 10 2024<\/u><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A practical guide to securing Android apps: storage, networking, the manifest, components, WebViews, deep links, dependencies, and release builds, with code you can use today.<\/p>\n","protected":false},"author":1,"featured_media":100003,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-100004","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/posts\/100004","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/comments?post=100004"}],"version-history":[{"count":0,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/posts\/100004\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/media\/100003"}],"wp:attachment":[{"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/media?parent=100004"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/categories?post=100004"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.nandee.ai\/blog\/wp-json\/wp\/v2\/tags?post=100004"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}