DEV Community

Cover image for Android App Security: A Practical Guide to Building Secure Android Applications
Padmakar Android
Padmakar Android

Posted on

Android App Security: A Practical Guide to Building Secure Android Applications

Building an Android application is not only about creating a good UI and
connecting it to an API.

Modern Android applications handle sensitive information such as
authentication tokens, personal data, financial information, files,
location data, and business-critical operations.

That makes security an architectural concern --- not something that
should be added just before release.

One principle every Android developer should understand is:

Never treat the Android client as a trusted environment.

The application runs on a device that the developer does not control.
The APK can be inspected, application behavior can be analyzed, network
requests can be observed, and locally stored data can potentially be
accessed on a compromised device.

So, what should we consider when building a secure Android application?


1. Never Store Secrets in the APK

One of the most common mistakes is putting secrets directly into
application code.

For example:

const val SECRET_KEY = "my-secret-key"
const val API_KEY = "123456789"
Enter fullscreen mode Exit fullscreen mode

At first glance, this might look harmless.

But Android applications are distributed to users as APKs or app bundles
that ultimately produce installable application code. An attacker can
obtain an APK and analyze its resources, strings, bytecode, and
behavior.

Even if you move the value into another file:

object AppConfig {
    const val API_SECRET = "super-secret-value"
}
Enter fullscreen mode Exit fullscreen mode

it is still part of the application.

Changing the location of the secret does not change the security model.

What should stay out of the application?

Examples include:

  • Database passwords
  • Private API credentials
  • Backend service credentials
  • Private cryptographic keys
  • Cloud service secrets
  • Signing credentials
  • Administrative credentials
  • Internal service authentication tokens

These should generally be kept on infrastructure controlled by the
backend.

What about API keys?

Not every API key is a secret.

Some public client identifiers are intentionally distributed with
applications. Certain SDKs require an application identifier that is not
designed to be confidential.

The important question is:

Does possession of this value allow an attacker to perform a
privileged operation?

If yes, don't treat the Android APK as a secure place to store it.


2. BuildConfig Is Not a Secret Store

A common approach is:

buildConfigField(
    "String",
    "API_KEY",
    "\"my-secret-key\""
)
Enter fullscreen mode Exit fullscreen mode

and then:

BuildConfig.API_KEY
Enter fullscreen mode Exit fullscreen mode

This can be useful for configuration, but it does not make a secret
secure.

The value is still packaged into the application.

The same applies to:

  • local.properties
  • gradle.properties
  • strings.xml
  • res/raw
  • assets/
  • BuildConfig
  • Kotlin constants

These can help with configuration management during development, but
they shouldn't be considered a secure runtime secret store.

A better architecture is:

                    ┌──────────────────┐
                    │   Android App    │
                    └────────┬─────────┘
                             │
                             │ Authenticated Request
                             ▼
                    ┌──────────────────┐
                    │     Backend      │
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │ Secret / Private │
                    │ Configuration    │
                    └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

The Android application receives only the information it actually needs.


3. Never Put Backend Credentials in the Mobile App

Consider an application that needs to communicate with:

Android App
     ↓
Backend API
     ↓
Internal Service
     ↓
Database
Enter fullscreen mode Exit fullscreen mode

A dangerous design would be:

Android App
     ↓
Internal Service
     ↓
Database
Enter fullscreen mode Exit fullscreen mode

with the Android application containing credentials for the internal
service.

If the APK is compromised, those credentials can potentially be
extracted and reused outside the application.

A better approach is:

Android App
     ↓
Authenticated Backend API
     ↓
Internal Services
     ↓
Database
Enter fullscreen mode Exit fullscreen mode

The backend becomes the security boundary.


4. Authentication Is Not Authorization

These two concepts are often confused.

Authentication

Authentication answers:

Who are you?

Examples include:

  • Username and password
  • OTP
  • Passkeys
  • OAuth
  • Access tokens
  • Biometric unlock

Authorization

Authorization answers:

What are you allowed to do?

For example:

User A → Can access User A's profile
User A → Cannot access User B's profile
Enter fullscreen mode Exit fullscreen mode

A user can be successfully authenticated and still be unauthorized to
access a particular resource.

A secure architecture should therefore look like:

Android App
     |
     | Request
     ▼
Backend
     |
     ├── Authenticate
     |
     ├── Authorize
     |
     ├── Validate Input
     |
     ├── Check Resource Ownership
     |
     ▼
Response
Enter fullscreen mode Exit fullscreen mode

Never depend exclusively on Android UI restrictions.

For example:

if (user.isAdmin) {
    showAdminPanel()
}
Enter fullscreen mode Exit fullscreen mode

This controls the UI.

It does not provide backend authorization.

An attacker can potentially bypass the UI and call the API directly.

The backend must enforce the permission.


5. Protect Access and Refresh Tokens

Modern applications commonly use access and refresh tokens.

A simplified model is:

Login
  ↓
Access Token
  ↓
API Requests

Refresh Token
  ↓
New Access Token
Enter fullscreen mode Exit fullscreen mode

The access token should be treated as sensitive information.

Avoid storing tokens carelessly:

sharedPreferences
    .edit()
    .putString("token", token)
    .apply()
Enter fullscreen mode Exit fullscreen mode

The appropriate storage strategy depends on the application's threat
model and token design.

For sensitive cryptographic keys, Android provides the Android
Keystore
system.

The architecture should consider:

Token
  ↓
Where is it stored?
  ↓
Who can access it?
  ↓
How long is it valid?
  ↓
Can it be revoked?
  ↓
What happens after logout?
Enter fullscreen mode Exit fullscreen mode

Security isn't simply about where the string is stored.

Token lifetime, revocation, rotation, server-side validation, and
session management are equally important.


6. Android Keystore

When cryptographic keys are required on Android, the Android Keystore
provides a platform mechanism for key management.

Conceptually:

Application
     |
     v
Crypto Operation
     |
     v
Android Keystore
     |
     v
Protected Key Material
Enter fullscreen mode Exit fullscreen mode

The application can use the key for cryptographic operations without
treating the key as an ordinary string that is freely passed around the
application.

For example, an application may generate a key:

val keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES,
    "AndroidKeyStore"
)

keyGenerator.init(
    KeyGenParameterSpec.Builder(
        "app_key",
        KeyProperties.PURPOSE_ENCRYPT or
            KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(
            KeyProperties.ENCRYPTION_PADDING_NONE
        )
        .build()
)

val secretKey = keyGenerator.generateKey()
Enter fullscreen mode Exit fullscreen mode

The exact cryptographic configuration should be chosen according to the
application's requirements.

The important lesson is:

Don't invent your own key-storage mechanism when the platform
already provides one.


7. Encryption Alone Does Not Make an Application Secure

Developers sometimes say:

"We use AES-256, so our data is secure."

That's incomplete.

You also need to understand:

  • How is the key generated?
  • Where is the key stored?
  • How is the key exchanged?
  • How is the key rotated?
  • What encryption mode is being used?
  • How are IVs/nonces generated?
  • How is integrity/authentication provided?
  • What happens if the key is compromised?
  • Can old data still be decrypted?
  • Is encryption being applied at the correct layer?

For example:

Data
 ↓
Encryption
 ↓
Storage
Enter fullscreen mode Exit fullscreen mode

is only part of the design.

You also need:

Key Management
       +
Access Control
       +
Integrity Protection
       +
Secure Storage
       +
Lifecycle Management
Enter fullscreen mode Exit fullscreen mode

Cryptography is a system, not simply an algorithm name.


8. Use Authenticated Encryption Where Appropriate

When encrypting application data, confidentiality alone is not always
enough.

You generally also need protection against unauthorized modification.

Conceptually:

Plaintext
    ↓
Encryption + Integrity Protection
    ↓
Ciphertext
Enter fullscreen mode Exit fullscreen mode

Authenticated encryption modes such as AES-GCM can provide
confidentiality and integrity together when used correctly.

Avoid designing your own encryption protocol.

For example, don't create a custom encryption scheme without
understanding the cryptographic requirements.

A custom cryptographic design can introduce vulnerabilities even when
the underlying algorithm is strong.


9. HTTPS Is Necessary --- But It Is Not Enough

Sensitive application traffic should use secure transport.

Instead of:

http://api.example.com
Enter fullscreen mode Exit fullscreen mode

production applications should use:

https://api.example.com
Enter fullscreen mode Exit fullscreen mode

But HTTPS does not automatically make the API secure.

Consider:

POST /api/users/123/profile
Enter fullscreen mode Exit fullscreen mode

Even with HTTPS, the backend must still determine:

Is the user authenticated?
        ↓
Is the user authorized?
        ↓
Does the resource belong to the user?
        ↓
Is this operation allowed?
        ↓
Is the request valid?
Enter fullscreen mode Exit fullscreen mode

So think of security as layers:

TLS
 ↓
Authentication
 ↓
Authorization
 ↓
Input Validation
 ↓
Business Rules
 ↓
Data Protection
Enter fullscreen mode Exit fullscreen mode

10. Don't Disable TLS Verification to Fix Development Problems

One of the most dangerous shortcuts is disabling certificate or hostname
validation because a development server isn't configured correctly.

Avoid production configurations that effectively say:

Accept any certificate
Accept any hostname
Trust everything
Enter fullscreen mode Exit fullscreen mode

This may make a development environment appear to work, but it removes
important security guarantees.

Instead:

Development Environment
        ↓
Proper Development Certificate

Production Environment
        ↓
Proper Production Certificate
Enter fullscreen mode Exit fullscreen mode

If your development environment uses self-signed certificates, configure
development trust intentionally rather than disabling validation
globally.


11. WebView Is an Important Attack Surface

WebView is extremely useful for applications that need to display web
content.

But WebView also introduces additional security considerations.

Potential areas include:

  • JavaScript
  • JavaScript interfaces
  • URL navigation
  • File access
  • Cookies
  • Redirects
  • Authentication
  • Local storage
  • External intents

If your application only needs to display trusted content, don't give
the WebView unnecessary capabilities.

For example, avoid blindly loading arbitrary URLs:

webView.loadUrl(userProvidedUrl)
Enter fullscreen mode Exit fullscreen mode

Instead, validate the URL before loading it.

Conceptually:

Requested URL
      |
      ▼
Is domain trusted?
      |
 ┌────┴────┐
 YES       NO
  |         |
  ▼         ▼
WebView   Reject
Enter fullscreen mode Exit fullscreen mode

12. JavaScript Interfaces Need Extra Care

Android allows applications to expose native functionality to
JavaScript.

For example:

webView.addJavascriptInterface(
    bridge,
    "AndroidBridge"
)
Enter fullscreen mode Exit fullscreen mode

This can be useful.

But it also creates a bridge between web content and native application
functionality.

If untrusted content can interact with that bridge, the security impact
can be significant.

Before exposing a JavaScript interface, ask:

Who controls the loaded page?

Can an attacker influence the URL?

What methods are exposed?

What data can those methods access?

Can those methods perform privileged operations?
Enter fullscreen mode Exit fullscreen mode

Only expose the minimum functionality required.


13. Deep Links Are Untrusted Input

Deep links are convenient:

myapp://article/123
Enter fullscreen mode Exit fullscreen mode

or:

https://example.com/article/123
Enter fullscreen mode Exit fullscreen mode

But the values coming from a deep link should be treated as untrusted
input.

Validate:

  • Scheme
  • Host
  • Path
  • Parameters
  • Authentication state
  • Authorization

For example:

Deep Link
    ↓
Parse
    ↓
Validate
    ↓
Authenticate
    ↓
Authorize
    ↓
Navigate
Enter fullscreen mode Exit fullscreen mode

Don't let a deep link directly perform a sensitive operation simply
because the application was launched using a trusted-looking URL.


14. Validate Server Responses Too

Security isn't only about validating input from users.

Applications should also carefully process server responses.

For example, don't assume that every response field will always contain
the expected value.

Instead of:

val amount = response.amount!!
Enter fullscreen mode Exit fullscreen mode

consider appropriate validation and error handling.

External systems can fail.

Servers can be misconfigured.

APIs can change.

Attackers can manipulate requests.

Defensive programming is an important part of application security.


15. Review Exported Android Components

Android applications contain components such as:

  • Activities
  • Services
  • Broadcast Receivers
  • Content Providers

For each component, ask:

Does this component need to be externally accessible?

Who can invoke it?

What input does it receive?

What permissions are required?

Does it expose sensitive functionality?
Enter fullscreen mode Exit fullscreen mode

If a component doesn't need to be externally accessible, don't expose it
unnecessarily.

Security configuration should be intentional.


16. Be Careful With Broadcast Receivers

Broadcast receivers can receive system or application broadcasts.

If sensitive functionality is exposed through a receiver, carefully
consider:

Who can send the broadcast?
What data does it contain?
Can another application trigger it?
Does it require permission?
Enter fullscreen mode Exit fullscreen mode

A receiver that performs privileged actions should not blindly trust
external broadcast data.


17. Don't Leak Sensitive Data Through Logs

Logging is essential during development.

But this is dangerous:

Log.d("AUTH", "Access Token = $accessToken")
Log.d("USER", "OTP = $otp")
Log.d("CRYPTO", "Key = $key")
Enter fullscreen mode Exit fullscreen mode

Avoid logging:

  • Access tokens
  • Refresh tokens
  • Passwords
  • OTP codes
  • Session IDs
  • Encryption keys
  • Personal information
  • Payment information
  • Sensitive API responses

A useful rule is:

If an attacker could use the information in your logs, don't log
it.

Also review logs before production release.


18. Be Careful With Screenshots and Sensitive UI

Some applications display sensitive information:

  • Banking information
  • Payment details
  • OTP
  • Private messages
  • Documents
  • Medical information

For particularly sensitive screens, consider whether screenshots or
screen capture should be allowed.

Android provides mechanisms such as:

window.setFlags(
    WindowManager.LayoutParams.FLAG_SECURE,
    WindowManager.LayoutParams.FLAG_SECURE
)
Enter fullscreen mode Exit fullscreen mode

But this should be applied based on actual requirements rather than
blindly across the entire application.

Security controls should match the sensitivity of the data.


19. Third-Party SDKs Are Part of Your Attack Surface

Your application isn't composed only of your own code.

A modern application might include:

Application Code
       +
Analytics SDK
       +
Crash Reporting
       +
Payment SDK
       +
Advertising SDK
       +
Networking Libraries
       +
Image Libraries
       +
Authentication SDK
       +
Build Plugins
Enter fullscreen mode Exit fullscreen mode

Every dependency introduces additional code and permissions.

Regularly review:

  • Dependency versions
  • Transitive dependencies
  • Known vulnerabilities
  • SDK permissions
  • Network behavior
  • Data collection
  • Build plugins
  • Unused dependencies

A useful mindset is:

If it ships inside my application, it is part of my attack
surface.


20. Secure the CI/CD Pipeline

Security doesn't end when application development is complete.

Consider the complete pipeline:

Developer
    ↓
Git Repository
    ↓
Pull Request
    ↓
CI/CD
    ↓
Dependencies
    ↓
Build
    ↓
Signing
    ↓
Release Artifact
    ↓
Distribution
Enter fullscreen mode Exit fullscreen mode

Every stage needs appropriate protection.

Important areas include:

  • Protected branches
  • Secret management
  • Dependency scanning
  • Secret scanning
  • Code review
  • CI/CD permissions
  • Signing key protection
  • Release access
  • Artifact integrity

For example, your signing credentials should never be committed to Git.

Use secure secret management in your CI/CD environment.


21. Protect the Android Signing Key

The signing key is extremely important.

If an attacker gains access to your production signing credentials, the
consequences can be serious.

Therefore:

Signing Key
     ↓
Secure Storage
     ↓
Restricted Access
     ↓
Controlled CI/CD Usage
Enter fullscreen mode Exit fullscreen mode

Don't distribute the production keystore unnecessarily across developer
machines.

Keep access limited to the people and systems that actually need it.


22. Assume the APK Can Be Reverse-Engineered

An APK is distributed to users.

Therefore:

Assume that someone can inspect your application.

A possible attack path is:

APK
 ↓
Decompilation
 ↓
Resource Analysis
 ↓
Code Analysis
 ↓
Runtime Analysis
 ↓
Application Behavior
Enter fullscreen mode Exit fullscreen mode

R8 can reduce and obfuscate application code:

minifyEnabled = true
Enter fullscreen mode Exit fullscreen mode

But obfuscation is not a replacement for security.

Don't depend on:

Obfuscation = Security
Enter fullscreen mode Exit fullscreen mode

Instead:

Secure Architecture
        +
Backend Authorization
        +
Secure Storage
        +
Cryptography
        +
Obfuscation
        +
Runtime Protections
Enter fullscreen mode Exit fullscreen mode

Each layer addresses a different problem.


23. Client-Side Security Checks Can Be Bypassed

Consider:

if (isPremiumUser) {
    showPremiumFeature()
}
Enter fullscreen mode Exit fullscreen mode

This controls the UI.

It does not necessarily protect the feature.

An attacker could potentially modify application behavior or directly
call the backend.

A secure architecture looks like:

Android Client
      |
      | Request
      ▼
Backend
      |
      | Verify entitlement
      ▼
Allow / Reject
Enter fullscreen mode Exit fullscreen mode

The client can improve user experience.

The backend should enforce business-critical security rules.


24. Don't Trust Client-Side Values

Never blindly trust values such as:

  • isAdmin
  • isPremium
  • isVerified
  • isAuthenticated
  • userId
  • role
  • price
  • discount
  • permission

For example, this is dangerous:

{
  "userId": "123",
  "isAdmin": true
}
Enter fullscreen mode Exit fullscreen mode

if the server simply trusts whatever the client sends.

Instead, security-sensitive properties should be determined and
validated by trusted backend systems.


25. Input Validation Matters

Anything coming from outside your trusted application boundary should be
considered untrusted.

Examples include:

  • User input
  • Deep links
  • Intent extras
  • Network responses
  • WebView URLs
  • Push notification data
  • Files
  • QR codes
  • Clipboard data

Validate:

  • Type
  • Length
  • Format
  • Range
  • Allowed characters
  • Business rules
  • Authorization

For example, if an API expects:

page = positive integer
Enter fullscreen mode Exit fullscreen mode

don't blindly accept unexpected or excessively large values.

Validation helps reduce both security issues and unexpected application
behavior.


26. Handle Sensitive Errors Carefully

Avoid exposing internal implementation details to users.

Instead of:

SQLException: connection failed at database server 10.0.0.4
Enter fullscreen mode Exit fullscreen mode

show:

Something went wrong. Please try again.
Enter fullscreen mode Exit fullscreen mode

Detailed information can be useful internally, but production users
should generally receive appropriate, non-sensitive error messages.


27. App Integrity Should Be Part of the Threat Model

For applications with higher security requirements, app-integrity
mechanisms can provide additional signals about the application and
environment.

Conceptually:

Android Application
        |
        | Integrity Evidence
        ▼
     Backend
        |
        +-- Validate
        |
        +-- Evaluate Risk
        |
        +-- Enforce Policy
Enter fullscreen mode Exit fullscreen mode

The important principle is:

Don't rely solely on a client-side boolean to determine whether the
application is trustworthy.

For sensitive operations, the backend should be able to enforce the
security decision.


28. Security Testing Should Be Continuous

Security testing shouldn't happen only before production release.

A useful development lifecycle is:

Design
  ↓
Threat Modeling
  ↓
Implementation
  ↓
Code Review
  ↓
Automated Security Checks
  ↓
Testing
  ↓
Release
  ↓
Monitoring
  ↓
Incident Response
Enter fullscreen mode Exit fullscreen mode

Consider testing:

  • Authentication
  • Authorization
  • Local storage
  • Network communication
  • WebView
  • Deep links
  • Exported components
  • Cryptography
  • Dependencies
  • Release configuration

The earlier a security problem is discovered, the easier it generally is
to fix.


29. Threat Modeling Before Writing Code

One of the most valuable security practices is threat modeling.

Before implementing a feature, ask:

What am I protecting?

Who might attack it?

What can the attacker control?

What happens if the attacker succeeds?

What security control reduces that risk?
Enter fullscreen mode Exit fullscreen mode

For example:

Asset:
Authentication Token

Threat:
Token Theft

Attack Surface:
Local Storage
Logs
Network
Memory
Backup

Impact:
Account Takeover

Controls:
Secure Storage
TLS
Token Expiration
Token Rotation
Backend Validation
Logging Restrictions
Enter fullscreen mode Exit fullscreen mode

This is much more useful than simply adding a security library because
someone recommended it.


A Practical Android Security Checklist

Before releasing an Android application, ask:

[ ] Are production secrets outside the APK?

[ ] Is authentication implemented securely?

[ ] Is authorization enforced by the backend?

[ ] Are access and refresh tokens protected?

[ ] Is sensitive local data protected?

[ ] Are cryptographic keys managed securely?

[ ] Is HTTPS configured correctly?

[ ] Is TLS verification enabled?

[ ] Are WebViews restricted?

[ ] Are JavaScript interfaces necessary and controlled?

[ ] Are deep links validated?

[ ] Are exported components intentional?

[ ] Are sensitive values excluded from logs?

[ ] Are screenshots restricted where necessary?

[ ] Have third-party dependencies been reviewed?

[ ] Are CI/CD secrets protected?

[ ] Are signing keys securely managed?

[ ] Has reverse engineering been considered?

[ ] Are client-side security checks backed by server-side enforcement?

[ ] Is user input validated?

[ ] Is the backend independently secure?

[ ] Are security tests part of the development lifecycle?

[ ] Has the application been threat-modeled?
Enter fullscreen mode Exit fullscreen mode

The Bigger Picture

Android security isn't a single library.

It isn't simply:

AES
+
HTTPS
+
R8
Enter fullscreen mode Exit fullscreen mode

A secure Android application requires multiple layers:

                    Security
                       │
       ┌───────────────┼───────────────┐
       │               │               │
   Application      Platform        Backend
    Security        Security        Security
       │               │               │
       ├─ Auth         ├─ Keystore     ├─ Authorization
       ├─ WebView      ├─ Permissions  ├─ API Security
       ├─ Deep Links   ├─ Components   ├─ Rate Limiting
       ├─ Storage      ├─ IPC          ├─ Validation
       └─ Logging      └─ Integrity    └─ Monitoring
Enter fullscreen mode Exit fullscreen mode

The most important mindset shift is this:

Don't ask only, "How do I make my Android app secure?"

Ask:

"What happens if someone controls the device running my
application?"

That question changes the way you design authentication, APIs, storage,
cryptography, WebViews, deep links, and even your CI/CD pipeline.

Security should not be the final layer added to an Android application.

Security should be one of the foundations on which the application is
built.


Final Thoughts

As Android developers, we spend a lot of time thinking about:

UI
Performance
Architecture
Networking
Database
Testing
Enter fullscreen mode Exit fullscreen mode

Security deserves the same level of attention.

A production application should be designed with the assumption that:

  • The client can be inspected.
  • User input can be manipulated.
  • Network requests can be replayed.
  • APK logic can be analyzed.
  • Dependencies can contain vulnerabilities.
  • Local data can be exposed on compromised devices.
  • Client-side checks can be bypassed.

The goal isn't to make an application impossible to attack.

The goal is to make the architecture resistant, observable, and
recoverable
when attacks happen.

For developers who want to go deeper into mobile security, a good
learning path is:

Android Security Fundamentals
          ↓
Cryptography Basics
          ↓
Authentication & Authorization
          ↓
Secure Storage
          ↓
Network Security
          ↓
WebView & Deep Link Security
          ↓
Reverse Engineering
          ↓
Threat Modeling
          ↓
OWASP MASVS
          ↓
OWASP MASTG
          ↓
Mobile Security Testing
Enter fullscreen mode Exit fullscreen mode

The best security engineers don't just know how to use security APIs.

They understand why the security control exists, what threat it
addresses, and what happens when that control fails.

Secure coding is a development skill.\
Security architecture is an engineering skill.

Android #Security #Kotlin #Cybersecurity


About the Author

Padmakar Garg is an Android Developer focused on Kotlin, Jetpack Compose, scalable application architecture, mobile security, and modern Android development. He writes about practical Android engineering, security, architecture, and real-world development challenges.

Connect with me on DEV to follow more articles about Android development, Kotlin, Jetpack Compose, Android security, and mobile architecture.

Top comments (0)