Remediation Guide Template
Android Application Security Remediation Guide
Developer Reference
Introduction
This guide provides detailed remediation steps for common Android security vulnerabilities. Each section includes vulnerable code examples, secure implementations, and references to official documentation.
Table of Contents
1. Data Storage
1.1 Secure SharedPreferences
Vulnerability: Plaintext data in SharedPreferences
Vulnerable Code:
// INSECURE - Plaintext storage
SharedPreferences prefs = getSharedPreferences("auth", MODE_PRIVATE);
prefs.edit().putString("password", password).apply();Secure Implementation:
// SECURE - EncryptedSharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
securePrefs.edit().putString("token", token).apply();Gradle Dependency:
implementation "androidx.security:security-crypto:1.1.0-alpha06"1.2 Secure Database Storage
Vulnerability: Unencrypted SQLite database
Secure Implementation (SQLCipher):
// SECURE - SQLCipher encrypted database
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;
public class SecureDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "secure.db";
private String passphrase;
public SecureDBHelper(Context context, String passphrase) {
super(context, DB_NAME, null, 1);
this.passphrase = passphrase;
SQLiteDatabase.loadLibs(context);
}
public SQLiteDatabase getSecureDatabase() {
return getWritableDatabase(passphrase.toCharArray());
}
}Gradle Dependency:
implementation "net.zetetic:android-database-sqlcipher:4.5.3"1.3 Disable Backup
Vulnerability: allowBackup="true" exposes data
Secure AndroidManifest.xml:
<application
android:allowBackup="false"
android:fullBackupContent="false"
...>Or with selective backup rules:
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules"
...>res/xml/backup_rules.xml:
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="auth.xml"/>
<exclude domain="database" path="credentials.db"/>
<exclude domain="file" path="secrets/"/>
</full-backup-content>2. Cryptography
2.1 Secure Key Storage
Vulnerability: Hardcoded encryption keys
Secure Implementation (Android Keystore):
// Generate key in Android Keystore
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(300)
.build();
keyGenerator.init(keySpec);
SecretKey key = keyGenerator.generateKey();2.2 Secure Encryption
Vulnerability: ECB mode or weak algorithms
Secure Implementation (AES-GCM):
// SECURE - AES-GCM encryption
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] ciphertext = cipher.doFinal(plaintext);
// Prepend IV to ciphertext
ByteBuffer buffer = ByteBuffer.allocate(iv.length + ciphertext.length);
buffer.put(iv);
buffer.put(ciphertext);
return buffer.array();
}
public byte[] decrypt(byte[] encrypted, SecretKey key) throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(encrypted);
byte[] iv = new byte[12]; // GCM IV is 12 bytes
buffer.get(iv);
byte[] ciphertext = new byte[buffer.remaining()];
buffer.get(ciphertext);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
return cipher.doFinal(ciphertext);
}2.3 Secure Password Hashing
Vulnerability: MD5/SHA1 for password hashing
Secure Implementation (Argon2):
// Using Argon2
import org.signal.argon2.Argon2;
import org.signal.argon2.Type;
import org.signal.argon2.Version;
Argon2 argon2 = new Argon2.Builder(Version.V13)
.type(Type.Argon2id)
.memoryCostKiB(65536)
.parallelism(4)
.iterations(3)
.build();
String hash = argon2.hash(password.toCharArray(), salt).getEncoded();3. Authentication
3.1 Secure Biometric Implementation
Vulnerability: Biometric bypass possible
Secure Implementation:
// SECURE - Biometric with CryptoObject binding
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Confirm your identity")
.setNegativeButtonText("Cancel")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build();
// Generate key requiring biometric auth
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
"biometric_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build();
// Create CryptoObject
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey("biometric_key", null);
cipher.init(Cipher.ENCRYPT_MODE, key);
BiometricPrompt.CryptoObject cryptoObject =
new BiometricPrompt.CryptoObject(cipher);
// Authenticate with CryptoObject
biometricPrompt.authenticate(promptInfo, cryptoObject);3.2 Secure Session Management
Vulnerability: Client-side session validation
Secure Implementation:
// SECURE - Server-side session validation
public class SessionManager {
private static final long SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
public boolean validateSession(String token) {
// Always validate with server
try {
Response response = api.validateToken(token);
return response.isSuccessful() && response.body().isValid();
} catch (Exception e) {
return false;
}
}
public void logout(String token) {
// Invalidate on server
api.invalidateToken(token);
// Clear local storage
EncryptedSharedPreferences.edit()
.remove("auth_token")
.remove("refresh_token")
.apply();
}
}4. Network Security
4.1 Certificate Pinning
Vulnerability: No certificate pinning
Secure Implementation (OkHttp):
// SECURE - Certificate pinning
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();Network Security Config:
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2025-01-01">
<pin digest="SHA-256">base64EncodedPin1=</pin>
<pin digest="SHA-256">base64EncodedPin2=</pin>
</pin-set>
</domain-config>
</network-security-config>4.2 Disable Cleartext Traffic
Vulnerable Configuration:
<!-- INSECURE -->
<application android:usesCleartextTraffic="true">Secure Configuration:
<!-- SECURE -->
<application android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config">5. Platform Security
5.1 Secure Exported Components
Vulnerability: Unprotected exported components
Secure Implementation:
<!-- Restrict with custom permission -->
<permission
android:name="com.example.app.SENSITIVE_PERMISSION"
android:protectionLevel="signature" />
<activity
android:name=".SensitiveActivity"
android:exported="true"
android:permission="com.example.app.SENSITIVE_PERMISSION" />
<!-- Or don't export if not needed -->
<activity
android:name=".InternalActivity"
android:exported="false" />5.2 Secure Content Providers
Vulnerability: SQL injection in content provider
Secure Implementation:
// SECURE - Parameterized queries
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
// Use query builder with parameterized selection
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("users");
// Restrict projection to known columns
String[] allowedProjection = {"id", "name", "email"};
projection = validateProjection(projection, allowedProjection);
return qb.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
}5.3 Secure Deep Links
Vulnerability: Deep link injection
Secure Implementation:
// SECURE - Validate deep link parameters
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri data = getIntent().getData();
if (data != null) {
// Validate scheme
if (!"https".equals(data.getScheme()) &&
!"myapp".equals(data.getScheme())) {
finish();
return;
}
// Validate host
if (!"trusted.example.com".equals(data.getHost())) {
finish();
return;
}
// Sanitize parameters
String param = data.getQueryParameter("action");
if (!isValidAction(param)) {
finish();
return;
}
processDeepLink(data);
}
}
private boolean isValidAction(String action) {
return action != null &&
action.matches("^[a-zA-Z0-9_]+$") &&
ALLOWED_ACTIONS.contains(action);
}6. Code Quality
6.1 Disable Debug Mode
Vulnerability: Debuggable in production
Secure build.gradle:
android {
buildTypes {
release {
debuggable false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}6.2 Remove Logging
Vulnerability: Sensitive data in logs
Secure Implementation:
// Use a logging wrapper
public class SecureLog {
private static final boolean DEBUG = BuildConfig.DEBUG;
public static void d(String tag, String message) {
if (DEBUG) {
Log.d(tag, sanitize(message));
}
}
private static String sanitize(String message) {
// Remove sensitive patterns
return message
.replaceAll("password=\\S+", "password=***")
.replaceAll("token=\\S+", "token=***");
}
}ProGuard rules:
# Remove logging in release
-assumenosideeffects class android.util.Log {
public static int d(...);
public static int v(...);
public static int i(...);
}References
Contact
For security questions regarding this guide:
- [Security Contact Information]