When your app is meant to help people in an emergency—a wildfire evacuation tool, a first-aid reference, or a crisis alert system—security isn't just about protecting data; it's about trust. A leaky permission or an unencrypted cache could turn a lifeline into a liability. But you have a deadline, probably a tight one, and a long list of feature requests. Where do you even start?
This guide is for the developer who needs a practical, no-nonsense checklist that fits into a sprint. We assume you're working with Android's modern tooling (Kotlin, Jetpack, or at least targeting API 30+), and you have a few hours to audit and fix the highest-risk areas. We won't cover every CVE or configuration flag—instead, we focus on the five steps that prevent the most common and dangerous vulnerabilities. Think of this as your emergency preparedness for app security: quick, actionable, and grounded in what actually breaks in real-world attacks.
We'll walk through data mapping, network hardening, local storage encryption, authentication best practices, and a lightweight verification routine. Each step includes concrete code snippets or configuration patterns you can adapt. By the end, you'll have a checklist you can run through in an afternoon—and a much safer app for your users.
1. Who Needs This Checklist and Why It Matters When Time Is Short
This checklist is designed for Android developers who are shipping under pressure—maybe you're a solo developer, part of a small team, or the only person responsible for security in a startup. You don't have a dedicated security engineer, and your next release is in a week. The scenarios we cover are common in emergency-response apps: handling location data, storing user profiles, communicating with cloud APIs, and possibly processing sensitive details like medical conditions or contact lists. If your app doesn't touch any of that, some steps may be overkill, but the principles still apply.
What happens when you skip these steps? The most immediate risk is data exposure. An attacker who decompiles your APK (which is trivially easy) can find hardcoded API keys, unencrypted database files, or insecure network configurations. They can intercept traffic on public Wi-Fi, extract credentials from SharedPreferences, or exploit a misconfigured WebView to inject malicious scripts. In an emergency context, the consequences are worse: a compromised app could send false alerts, leak a user's home address, or prevent critical notifications from reaching them. We've seen real-world examples where a poorly secured emergency app was used to track users or spread misinformation.
But it's not just about the worst-case scenario. Even minor vulnerabilities erode user trust. If your app asks for permissions that don't make sense, or if it sends data in plaintext, users will uninstall it and leave negative reviews. And regulators are paying attention: GDPR, CCPA, and similar laws impose fines for data breaches, even if the app is free. So this checklist is also a business decision—a way to reduce liability and build a reputation for reliability.
We've organized the steps so you can start anywhere, but we recommend following the order we present: first understand what data you have, then secure it in transit, then at rest, then through authentication, and finally verify your work. Each step builds on the previous one, and the verification step at the end catches gaps you might have missed. If you only have time for three steps, do steps 1, 2, and 4—they cover the most common attack vectors.
2. Prerequisites: What You Need Before You Start Hardening Your App
Before diving into code changes, take 15 minutes to set up your environment and gather the right tools. Here's what we recommend:
- Android Studio with the latest SDK tools. You'll need the APK Analyzer, Logcat, and the built-in network inspector. If you're on an older version, update it—many security checks rely on recent lint rules.
- Access to your app's source code and build configuration. You'll be editing the AndroidManifest.xml, Gradle files, and Java/Kotlin source files. Make sure you have a clean build before you start.
- OWASP Mobile Top 10 (2024 edition). Keep this reference open in a browser tab. It lists the most critical risks like M1 (Improper Platform Usage), M2 (Insecure Data Storage), and M3 (Insecure Communication). Our checklist maps directly to these categories.
- A test device or emulator running Android 12 or later. Newer Android versions enforce stricter security defaults, so testing on an older device might give you a false sense of security. If you must target older APIs, you'll need to compensate with additional hardening.
You should also have a basic understanding of Android's permission model, encryption APIs (like Jetpack Security), and network libraries (like OkHttp or Retrofit). If any of that is unfamiliar, keep the official Android developer docs open—they're well-written and up-to-date. Finally, carve out at least four uninterrupted hours. Security hardening requires focus, and context switching will lead to missed steps.
One more thing: if your app uses third-party libraries (analytics, crash reporting, push notifications), review their security posture too. A vulnerability in a library is a vulnerability in your app. Check if they follow best practices like certificate pinning or data minimization. If they don't, consider alternatives or wrap them with extra safeguards. For example, if your analytics SDK sends raw location data over HTTP, you need to either disable that feature or encrypt it yourself.
We also recommend setting up a simple threat model: list the app's data flows (where data enters, where it's stored, where it's sent) and identify the most valuable targets for an attacker. For an emergency app, that's usually user location, personal contact information, and authentication tokens. Focus your efforts on protecting those assets first. You can always expand later.
3. The Core Workflow: Five Steps to Secure Your Android App
Here's the step-by-step process. Each step includes a specific action item and a code or configuration example.
Step 1: Map Your Data and Minimize Exposure
Start by auditing every data type your app collects, stores, or transmits. Look at your model classes, database schemas, and network request bodies. Ask yourself: does this data need to be on the device at all? Can we use ephemeral storage (like a temporary file) instead of persisting it? For example, if your app downloads a map of evacuation routes, consider caching it in the app's internal storage with encryption, and clear it after the user closes the app. If you don't need it, don't keep it.
Next, review your AndroidManifest.xml for permissions. Remove any that aren't strictly necessary. A common mistake is declaring ACCESS_FINE_LOCATION but only using coarse location. Be explicit: if you only need approximate location, use ACCESS_COARSE_LOCATION. Also, consider using the android:maxSdkVersion attribute to restrict permissions to only the API levels that need them. For example, if you target API 29+, you can stop requesting READ_EXTERNAL_STORAGE because scoped storage handles file access.
Step 2: Secure Network Communication
By default, Android enforces HTTPS for all cleartext traffic starting from API 28 (Android 9). But your app might still be using HTTP for localhost or development servers. In production, you should never transmit sensitive data over unencrypted channels. Configure your app to use HTTPS with TLS 1.2 or higher. If you use OkHttp, enable certificate pinning to prevent man-in-the-middle attacks. Here's a snippet:
val certificatePinner = CertificatePinner.Builder()
.add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
But be careful: if your certificate changes, you'll need to update the pin or your app will fail to connect. Use a backup pin and monitor expiration dates. For apps that rely on emergency APIs (like weather alerts or shelter locations), consider using a fallback endpoint if the primary one's certificate changes unexpectedly.
Also, disable cleartext traffic for all domains except perhaps local development. Add this to your AndroidManifest.xml inside the <application> tag: android:usesCleartextTraffic="false". If you need to allow specific domains (e.g., for a staging server), use a network security config file.
Step 3: Encrypt Local Storage
Android's internal storage is sandboxed per app, but that doesn't protect against a compromised device or a malicious app with root access. For sensitive data, use the Jetpack Security library to encrypt files and SharedPreferences. Here's how to encrypt a preferences file:
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context, "secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
For database files, consider using SQLCipher or Room with encryption. The Jetpack Security library also provides EncryptedFile for larger data. Avoid storing secrets like API keys in plaintext—use the Android Keystore system to generate and store cryptographic keys. And never put keys in version control; use environment variables or a secure build server.
Step 4: Implement Strong Authentication and Session Management
If your app requires user login, use modern authentication protocols like OAuth 2.0 with PKCE (Proof Key for Code Exchange). Avoid rolling your own authentication—it's too easy to get wrong. For token storage, use the EncryptedSharedPreferences from step 3, not SharedPreferences or a custom file. Set short expiration times for access tokens (15–30 minutes) and use refresh tokens for longer sessions. When the user logs out, clear all tokens and any cached data.
Also, implement biometric authentication for sensitive actions, like viewing personal information or confirming an emergency contact. Android's BiometricPrompt API handles this elegantly. Here's a minimal example:
val biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
// proceed with sensitive action
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Verify your identity")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
For emergency apps, consider allowing a grace period after biometric failure (e.g., fallback to PIN) so users can still access critical features in a crisis. But log these events for security auditing.
Step 5: Verify Your Defenses with Automated Checks and Manual Testing
Once you've made the changes, run Android's built-in lint and the ./gradlew lintVitalRelease command to catch common security issues like exposed activities or missing permissions. Then use a tool like MobSF (Mobile Security Framework) to scan your APK for vulnerabilities. MobSF runs locally and checks for hardcoded secrets, insecure network settings, and more. Alternatively, use the free tier of a cloud service like NowSecure.
Finally, perform a manual test: install your app on a test device, intercept its traffic with a proxy like Burp Suite (configured with your custom CA), and verify that all connections are encrypted and that no sensitive data leaks in logs or error messages. Also, try to access stored data by pulling the app's private directory via ADB (if the device is rooted or debug mode). If you can read plaintext data, you need to encrypt it. Document any findings and fix them before release.
4. Tools, Setup, and Environment Realities for Busy Developers
The tools you choose can make or break your security workflow. Here's what we recommend based on real-world constraints:
- Static Analysis: Android Lint (built-in) and MobSF (free, open-source). MobSF can analyze both source code and the compiled APK. It generates a report with severity levels and actionable advice. We've found it catches about 80% of common issues.
- Dynamic Analysis: Burp Suite Community Edition (free for basic proxying) or mitmproxy (open-source). Both allow you to inspect HTTPS traffic if you install a custom CA on your test device. For emergency apps, test network resilience by simulating poor connectivity—your app should gracefully handle timeouts and retries.
- Vulnerability Scanning: Google's own Security Test Suite (part of the Android Testing Library) or OWASP ZAP for API testing. These are more advanced but worth the setup time for critical apps.
One reality you'll face: security tools often flag false positives. For example, a lint warning about an exported activity might be intentional for a deep link. Don't blindly fix everything; understand the context. Use a risk-based approach: fix anything that could lead to data exposure or privilege escalation, and document the rest with a rationale.
Another practical concern is build time. Running MobSF on every commit is too slow for most teams. Instead, integrate security checks into your CI/CD pipeline for release branches only. Use a Gradle plugin like dependency-check to scan for known vulnerabilities in your libraries. And keep an eye on Google Play's security alerts—they'll notify you if your app violates policies.
For teams working on a legacy codebase, you may encounter outdated dependencies that are hard to upgrade. In that case, prioritize code-level mitigations: if you can't update a library, wrap its insecure parts with your own validation. For example, if an old HTTP client doesn't support certificate pinning, add a custom interceptor that validates the certificate hash.
Finally, remember that security is not a one-time task. Set a recurring calendar reminder to review your threat model and update dependencies every quarter. For emergency apps, also review after any major OS update or security advisory. This checklist gives you a solid foundation, but staying secure requires ongoing attention.
5. Variations for Different Constraints: When You Can't Do Everything
Not every project can implement all five steps before launch. Here are common scenarios and what to prioritize:
Scenario A: Solo Developer, Shipping Tomorrow
If you have only a few hours, focus on steps 1 and 2 (data mapping and network security). Disable cleartext traffic, remove unnecessary permissions, and ensure all API calls use HTTPS. Then, quickly encrypt any SharedPreferences that store tokens or user details using Jetpack Security. Skip certificate pinning for now—it's risky without a backup plan. After launch, schedule a security sprint within two weeks to add the remaining steps.
Scenario B: Team of Three, Tight Deadline but Some Budget
You can afford to do all five steps, but you'll need to parallelize. Have one developer handle data mapping and permissions, another focus on network security and encryption, and a third tackle authentication and testing. Use a shared checklist (like a Trello board) to track progress. Allocate a full day for testing and fixing regressions. The verification step is critical here—don't skip it.
Scenario C: Legacy App with No Unit Tests
This is the riskiest scenario. Start by running MobSF on the current APK to get a baseline of vulnerabilities. Then, prioritize fixing the highest-severity issues: hardcoded secrets, insecure data storage, and cleartext traffic. For each fix, add a simple unit test to prevent regression. You won't be able to refactor the entire app, so focus on the data flows that handle sensitive information. Consider using a feature flag to disable insecure features until they're hardened.
In all scenarios, communicate with your stakeholders about what you're doing. Tell them that security hardening is not optional for an emergency app—it's part of the product. If they push back, use the threat model to explain the real-world consequences of a breach. Most product managers will understand once you frame it as user safety.
One more variation: if your app is open-source, security becomes even more critical because attackers can study your code. In that case, add a security policy file to your repository (SECURITY.md) that explains how to report vulnerabilities. Also, consider using a bug bounty program if you have the resources.
6. Pitfalls, Debugging, and What to Check When Your Fixes Break Something
Even with a solid checklist, things can go wrong. Here are the most common pitfalls and how to handle them:
Pitfall: Certificate Pinning Causes Outages
If your certificate pin is hardcoded and the server's certificate changes (e.g., after renewal), your app will fail to connect. This is especially dangerous for emergency apps that need to be reliable. Mitigation: use a backup pin and a fallback mechanism. For example, if the primary pin fails, try the backup, and if both fail, prompt the user to update the app. Alternatively, use public key pinning with a grace period. Or, consider using a library like TrustKit that lets you update pins remotely via a configuration file.
Pitfall: Encrypted Storage Slows Down App Startup
Encrypting SharedPreferences or files adds overhead. If your app reads data on every launch, consider using a lazy initialization pattern: only encrypt the most sensitive fields, and leave non-sensitive data in plaintext. Also, use the EncryptedSharedPreferences with a reasonable key size (AES-256 is fine) and avoid encrypting large blobs in preferences—use EncryptedFile instead.
Pitfall: Biometric Authentication Locks Out Users in Emergencies
If your app requires biometric auth for every sensitive action, a user with a damaged fingerprint sensor or a face mask might be unable to proceed. Always provide a fallback (PIN, pattern, or password) and allow the user to bypass biometrics for a limited time after successful authentication. Log these bypasses for security auditing, but don't block access in a crisis.
Pitfall: Lint Rules Are Too Strict and Block Builds
Some lint rules, like ExportedContentProvider or UnprotectedBroadcastReceiver, can cause build failures if you have legitimate use cases. Instead of disabling the rule globally, add a @SuppressLint annotation with a comment explaining why the warning is a false positive. This keeps the rule active for other parts of the codebase.
When something breaks, use Logcat to trace the issue. If an encrypted preference can't be read, check that the master key is still accessible—sometimes a device backup restore can invalidate keys. If a network request fails, inspect the TLS handshake with a tool like Wireshark or the Android Studio Network Inspector. And if your app crashes after adding a security library, verify that you've added the correct dependencies and that your minimum SDK version is compatible.
Finally, don't forget to test on a variety of devices. Security behavior can differ between Android versions and manufacturers. For example, some Samsung devices handle biometric prompts differently than Pixel phones. Test on at least two devices running different Android versions (e.g., 12 and 14). And always test on a device with Google Play Services, because many security APIs depend on it.
After you ship, monitor your crash reporting and user feedback for any security-related issues. If you receive a report of a vulnerability, have a process in place to triage and fix it quickly. For emergency apps, consider a dedicated security contact email and a response SLA of 24 hours.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!