← Back to books

Chapter 1: Android Security Fundamentals

Security is not a feature you add at the end of development—it's a mindset that shapes every decision you make from the first line of code. Before diving into specific security implementations, you need to understand how Android protects applications at the system level and where those protections fall short.

This chapter establishes the foundation for everything that follows. You'll learn how Android's security architecture works, how the permission system protects users, and how to think about threats systematically. By the end, you'll understand not just what to secure, but why Android's security model works the way it does.


1.1 Android Security Architecture Overview

Android's security is built on multiple layers, each providing defense against different types of attacks. Understanding these layers helps you identify where your app's security responsibilities begin.

1.1.1 The Security Stack

┌─────────────────────────────────────────────────┐
│              Application Layer                   │
│         (Your App's Security Logic)              │
├─────────────────────────────────────────────────┤
│            Android Framework                     │
│    (Permissions, Crypto APIs, Keystore)          │
├─────────────────────────────────────────────────┤
│         Native Libraries & ART                   │
│      (Memory Safety, Code Verification)          │
├─────────────────────────────────────────────────┤
│              Linux Kernel                        │
│    (Process Isolation, SELinux, Seccomp)         │
├─────────────────────────────────────────────────┤
│         Hardware Security                        │
│   (TEE, StrongBox, Secure Element, TrustZone)    │
└─────────────────────────────────────────────────┘

Each layer builds upon the security guarantees of the layer below it. As an app developer, you primarily work at the Application and Framework layers, but the lower layers provide critical protections that make your security code meaningful.

1.1.2 Linux Kernel Security

At its core, Android is a Linux-based operating system. This provides several fundamental security mechanisms:

Process Isolation

Every Android application runs in its own process with a unique Linux user ID (UID). This isolation means:

  • Apps cannot directly access other apps' memory
  • File system permissions prevent unauthorized data access
  • System resources are protected from unprivileged apps
// Each app gets a unique UID at install time
// Format: u0_a{number} for user apps
// Example: u0_a123

// You can check your app's UID programmatically
val myUid = android.os.Process.myUid()
Log.d("Security", "My UID: $myUid")

SELinux (Security-Enhanced Linux)

Android uses SELinux in enforcing mode to provide Mandatory Access Control (MAC). Unlike traditional Linux permissions (Discretionary Access Control), SELinux policies cannot be overridden by the file owner.

# SELinux denies access even if file permissions allow it
# Example policy (simplified):
# allow untrusted_app app_data_file:file { read write };
# deny untrusted_app system_data_file:file { read write };

SELinux policies define:

  • Which processes can access which files
  • What system calls an app can make
  • How apps can communicate with system services

Seccomp (Secure Computing)

Seccomp filters restrict which system calls an application can make. Android uses seccomp-bpf to block dangerous system calls that could be used for exploitation:

# Example: These system calls are blocked for apps
# - ptrace (process debugging)
# - mount (filesystem manipulation)
# - reboot (system control)

1.1.3 Application Sandbox

The application sandbox is Android's primary security mechanism. Each app operates within its own sandbox with:

Resource Isolation Level Notes
Process Complete Separate Linux process per app
User ID Unique Each app gets its own UID
File Storage Isolated /data/data/<package>/ is private
Memory Protected No direct access to other apps' memory
Network Shared Requires INTERNET permission
Hardware Mediated Accessed through system services

Sandbox Boundaries:

┌──────────────────────────────────────────────────────┐
│                    Your App Sandbox                   │
│  ┌─────────────────────────────────────────────────┐ │
│  │  /data/data/com.yourapp/                        │ │
│  │  ├── shared_prefs/    (Private)                 │ │
│  │  ├── databases/       (Private)                 │ │
│  │  ├── files/           (Private)                 │ │
│  │  └── cache/           (Private)                 │ │
│  └─────────────────────────────────────────────────┘ │
│                         │                             │
│              System Services (Binder IPC)             │
│                         │                             │
│         ┌───────────────┴───────────────┐            │
│         ▼                               ▼            │
│   Camera Service                  Location Service   │
│   (Permission Required)           (Permission Required)│
└──────────────────────────────────────────────────────┘

1.1.4 Hardware-Backed Security

Modern Android devices include dedicated hardware for security operations:

Trusted Execution Environment (TEE)

The TEE is an isolated execution environment that runs alongside the main operating system. It provides:

  • Secure key storage
  • Cryptographic operations
  • Biometric data processing
  • DRM content protection
// Check if hardware-backed keystore is available
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)

val keyInfo = keyStore.getKey("my_key", null)?.let { key ->
    val factory = KeyFactory.getInstance(key.algorithm, "AndroidKeyStore")
    factory.getKeySpec(key, KeyInfo::class.java) as KeyInfo
}

val isHardwareBacked = keyInfo?.isInsideSecureHardware == true

StrongBox

StrongBox is a tamper-resistant hardware security module found in devices with dedicated secure processors (like Google's Titan M chip):

// Request StrongBox-backed key generation
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
    "strongbox_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setIsStrongBoxBacked(true)  // Request StrongBox
    .build()

try {
    val keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES,
        "AndroidKeyStore"
    )
    keyGenerator.init(keyGenParameterSpec)
    keyGenerator.generateKey()
} catch (e: StrongBoxUnavailableException) {
    // Fall back to TEE-backed key
    Log.w("Security", "StrongBox not available, using TEE")
}

1.2 The Android Permission System

Permissions are the primary mechanism for protecting user privacy and controlling access to sensitive resources. Understanding how permissions work—and their limitations—is essential for secure app development.

1.2.1 Permission Architecture

Android permissions are organized into protection levels:

Protection Level Description User Interaction Example
Normal Low-risk permissions Granted automatically INTERNET, BLUETOOTH
Dangerous Access to sensitive data Runtime prompt required CAMERA, LOCATION
Signature Same signing certificate Automatic if signed same System-level APIs
Privileged System apps only Pre-installed apps MANAGE_USERS

Permission Declaration:

<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.secureapp">
    
    <!-- Normal permissions - granted automatically -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <!-- Dangerous permissions - require runtime request -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    
    <!-- Permission with max SDK (for backward compatibility) -->
    <uses-permission 
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />
        
</manifest>

1.2.2 Runtime Permissions (Android 6.0+)

Since Android 6.0 (API 23), dangerous permissions must be requested at runtime:

class MainActivity : AppCompatActivity() {
    
    private val cameraPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            openCamera()
        } else {
            handlePermissionDenied()
        }
    }
    
    private val multiplePermissionsLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val allGranted = permissions.values.all { it }
        if (allGranted) {
            startLocationTracking()
        } else {
            handlePartialPermissions(permissions)
        }
    }
    
    private fun requestCameraPermission() {
        when {
            // Already granted
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED -> {
                openCamera()
            }
            
            // Should show rationale
            shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
                showPermissionRationaleDialog()
            }
            
            // Request permission
            else -> {
                cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }
    
    private fun requestLocationPermissions() {
        val permissions = arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
        
        // Check if already granted
        val allGranted = permissions.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
        
        if (allGranted) {
            startLocationTracking()
        } else {
            multiplePermissionsLauncher.launch(permissions)
        }
    }
    
    private fun handlePermissionDenied() {
        if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
            // User selected "Don't ask again" - direct to settings
            showSettingsDialog()
        } else {
            // Show explanation and retry option
            showPermissionDeniedMessage()
        }
    }
    
    private fun showSettingsDialog() {
        AlertDialog.Builder(this)
            .setTitle("Permission Required")
            .setMessage("Camera permission is required. Please enable it in Settings.")
            .setPositiveButton("Settings") { _, _ ->
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data = Uri.fromParts("package", packageName, null)
                startActivity(intent)
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

1.2.3 Permission Best Practices

Principle of Least Privilege:

Request only the permissions you absolutely need, when you need them:

// ❌ BAD: Requesting all permissions at app launch
class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Don't do this - users will deny permissions they don't understand
        requestAllPermissions()
    }
}

// ✅ GOOD: Request permissions in context
class PhotoActivity : AppCompatActivity() {
    
    private fun onTakePhotoClicked() {
        // User understands why camera is needed
        if (hasCameraPermission()) {
            takePhoto()
        } else {
            requestCameraPermission()
        }
    }
}

Permission Groups (Android 11+):

Android groups related permissions together. Granting one permission in a group may automatically grant others:

// Location permission group
// ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION are in the same group
// But ACCESS_BACKGROUND_LOCATION requires separate request

private fun requestBackgroundLocation() {
    // First, ensure foreground location is granted
    if (hasForegroundLocationPermission()) {
        // Then request background location separately
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            backgroundLocationLauncher.launch(
                Manifest.permission.ACCESS_BACKGROUND_LOCATION
            )
        }
    } else {
        // Request foreground first
        requestForegroundLocation()
    }
}

1.2.4 One-Time Permissions (Android 11+)

Users can grant permissions for a single session only:

// Check permission status for appropriate handling
private fun checkPermissionStatus(permission: String): PermissionStatus {
    return when {
        ContextCompat.checkSelfPermission(this, permission) == 
            PackageManager.PERMISSION_GRANTED -> PermissionStatus.GRANTED
        
        shouldShowRequestPermissionRationale(permission) -> 
            PermissionStatus.DENIED_CAN_ASK
        
        else -> PermissionStatus.DENIED_PERMANENTLY
    }
}

enum class PermissionStatus {
    GRANTED,
    DENIED_CAN_ASK,      // User denied but can ask again
    DENIED_PERMANENTLY   // User selected "Don't ask again" or one-time expired
}

// Handle one-time permission gracefully
override fun onResume() {
    super.onResume()
    
    // Re-check permissions on resume - they may have been revoked
    if (!hasCameraPermission()) {
        // Camera was a one-time grant that expired
        showCameraPermissionNeeded()
    }
}

1.2.5 Permission Auto-Reset (Android 11+)

Unused apps automatically have their permissions revoked:

// Check if your app is exempt from auto-reset
private fun checkAutoResetStatus() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        val packageManager = packageManager
        val isExempt = packageManager.isAutoRevokeWhitelisted
        
        if (!isExempt) {
            // Permissions may be auto-reset if app is unused
            // Consider reminding users to open the app periodically
            // Or request exemption if critical (e.g., health monitoring app)
        }
    }
}

// Request exemption (use sparingly - requires justification)
private fun requestAutoResetExemption() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
        intent.data = Uri.fromParts("package", packageName, null)
        startActivity(intent)
        // User must manually disable "Pause app activity if unused"
    }
}

1.3 Security Changes Across Android Versions

Android's security model evolves with each release. Understanding these changes ensures your app remains secure and functional across versions.

1.3.1 Security Evolution Timeline

Android Version API Key Security Changes
6.0 Marshmallow 23 Runtime permissions
7.0 Nougat 24 File-based encryption, Network Security Config
8.0 Oreo 26 Background execution limits, autofill framework
9.0 Pie 28 TLS by default, biometric API
10 Q 29 Scoped storage, background location limits
11 R 30 One-time permissions, package visibility
12 S 31 Approximate location, bluetooth permissions
12L 32 Extended security for large screens
13 T 33 Photo picker, notification permissions
14 U 34 Credential manager, partial photo access
15 V 35 Enhanced privacy dashboard, file integrity

1.3.2 Handling Version-Specific Security

object SecurityCompat {
    
    /**
     * Check if device supports hardware-backed keystore
     */
    fun hasHardwareBackedKeystore(): Boolean {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
    }
    
    /**
     * Check if StrongBox is available
     */
    fun hasStrongBox(context: Context): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            context.packageManager.hasSystemFeature(
                PackageManager.FEATURE_STRONGBOX_KEYSTORE
            )
        } else {
            false
        }
    }
    
    /**
     * Check if biometric authentication is available
     */
    fun hasBiometricCapability(context: Context): BiometricCapability {
        return when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
                val biometricManager = BiometricManager.from(context)
                when (biometricManager.canAuthenticate(
                    BiometricManager.Authenticators.BIOMETRIC_STRONG
                )) {
                    BiometricManager.BIOMETRIC_SUCCESS -> 
                        BiometricCapability.STRONG
                    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> 
                        BiometricCapability.NOT_ENROLLED
                    else -> 
                        BiometricCapability.NOT_AVAILABLE
                }
            }
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
                @Suppress("DEPRECATION")
                val fingerprintManager = context.getSystemService(
                    FingerprintManager::class.java
                )
                if (fingerprintManager?.isHardwareDetected == true) {
                    if (fingerprintManager.hasEnrolledFingerprints()) {
                        BiometricCapability.WEAK
                    } else {
                        BiometricCapability.NOT_ENROLLED
                    }
                } else {
                    BiometricCapability.NOT_AVAILABLE
                }
            }
            else -> BiometricCapability.NOT_AVAILABLE
        }
    }
    
    /**
     * Get appropriate storage location based on Android version
     */
    fun getSecureStorageDir(context: Context): File {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // Use credential-encrypted storage
            val ceContext = context.createCredentialProtectedStorageContext()
            ceContext.filesDir
        } else {
            context.filesDir
        }
    }
}

enum class BiometricCapability {
    STRONG,         // Class 3 biometrics (fingerprint, face with depth)
    WEAK,           // Class 2 biometrics
    NOT_ENROLLED,   // Hardware available but not set up
    NOT_AVAILABLE   // No biometric hardware
}

1.3.3 Network Security Configuration (Android 7.0+)

Control your app's network security behavior declaratively:

<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    
    <!-- Base configuration for all connections -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    
    <!-- Domain-specific configuration -->
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.yourcompany.com</domain>
        
        <!-- Certificate pinning -->
        <pin-set expiration="2025-01-01">
            <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
            <!-- Backup pin (REQUIRED) -->
            <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
        </pin-set>
    </domain-config>
    
    <!-- Debug-only overrides -->
    <debug-overrides>
        <trust-anchors>
            <!-- Trust user-installed CA certificates for debugging -->
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
    
</network-security-config>
<!-- AndroidManifest.xml -->
<application
    android:networkSecurityConfig="@xml/network_security_config"
    ... >

1.4 Threat Modeling for Android Applications

Secure development starts with understanding what you're protecting against. Threat modeling helps you identify vulnerabilities before attackers do.

1.4.1 The STRIDE Model

STRIDE is a framework for categorizing security threats:

Threat Description Android Example
Spoofing Impersonating a user or system Fake login screens, stolen tokens
Tampering Modifying data or code APK modification, man-in-the-middle
Repudiation Denying actions occurred Fraudulent transactions without logs
Information Disclosure Unauthorized data access Leaking PII, exposed API keys
Denial of Service Making system unavailable Resource exhaustion, crashes
Elevation of Privilege Gaining unauthorized access Root exploits, permission bypass

1.4.2 Android-Specific Threat Landscape

Attack Surfaces:

┌─────────────────────────────────────────────────────────────┐
│                     ATTACK SURFACES                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │   Network    │  │   Storage    │  │     IPC      │       │
│  │              │  │              │  │              │       │
│  │ • MITM       │  │ • Root       │  │ • Intent     │       │
│  │ • SSL Strip  │  │ • Backup     │  │   Injection  │       │
│  │ • DNS Spoof  │  │ • SD Card    │  │ • Broadcast  │       │
│  │ • API Abuse  │  │ • Logs       │  │   Sniffing   │       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │   Binary     │  │    Input     │  │   Device     │       │
│  │              │  │              │  │              │       │
│  │ • Reverse    │  │ • SQL        │  │ • Rooted     │       │
│  │   Engineer   │  │   Injection  │  │ • Emulator   │       │
│  │ • Tampering  │  │ • XSS        │  │ • USB Debug  │       │
│  │ • Debugging  │  │ • Deep Links │  │ • Screen     │       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

1.4.3 Creating a Threat Model

Step 1: Identify Assets

/**
 * Document what you're protecting
 */
data class SecurityAsset(
    val name: String,
    val sensitivity: Sensitivity,
    val storageLocation: StorageLocation,
    val threats: List<String>
)

enum class Sensitivity { LOW, MEDIUM, HIGH, CRITICAL }
enum class StorageLocation { MEMORY, PREFERENCES, DATABASE, FILE, NETWORK }

val securityAssets = listOf(
    SecurityAsset(
        name = "Authentication Token",
        sensitivity = Sensitivity.CRITICAL,
        storageLocation = StorageLocation.PREFERENCES,
        threats = listOf("Token theft", "Session hijacking", "Replay attacks")
    ),
    SecurityAsset(
        name = "User PII",
        sensitivity = Sensitivity.HIGH,
        storageLocation = StorageLocation.DATABASE,
        threats = listOf("Data breach", "Unauthorized access", "Backup extraction")
    ),
    SecurityAsset(
        name = "API Keys",
        sensitivity = Sensitivity.CRITICAL,
        storageLocation = StorageLocation.MEMORY,
        threats = listOf("Key extraction", "Reverse engineering", "Traffic analysis")
    )
)

Step 2: Identify Threat Actors

Actor Capability Motivation Example Attack
Script Kiddie Low Curiosity, bragging Public exploit tools
Competitor Medium Business intelligence Reverse engineering
Cybercriminal High Financial gain Account takeover
Nation State Very High Espionage Zero-day exploits
Malicious Insider Varies Revenge, profit Direct data access

Step 3: Document Attack Scenarios

data class ThreatScenario(
    val id: String,
    val threat: String,
    val attackVector: String,
    val asset: String,
    val likelihood: Int,      // 1-5
    val impact: Int,          // 1-5
    val mitigation: String
) {
    val riskScore: Int get() = likelihood * impact
}

val threatScenarios = listOf(
    ThreatScenario(
        id = "T001",
        threat = "Token Theft via Backup",
        attackVector = "ADB backup extraction on unencrypted storage",
        asset = "Authentication Token",
        likelihood = 4,
        impact = 5,
        mitigation = "Use EncryptedSharedPreferences, disable backup"
    ),
    ThreatScenario(
        id = "T002",
        threat = "Man-in-the-Middle Attack",
        attackVector = "SSL interception on public WiFi",
        asset = "API Communications",
        likelihood = 3,
        impact = 4,
        mitigation = "Certificate pinning, TLS 1.3 enforcement"
    ),
    ThreatScenario(
        id = "T003",
        threat = "Intent Injection",
        attackVector = "Malicious app sends crafted intents",
        asset = "Application Logic",
        likelihood = 3,
        impact = 3,
        mitigation = "Validate all intent data, use explicit intents"
    ),
    ThreatScenario(
        id = "T004",
        threat = "API Key Extraction",
        attackVector = "APK decompilation and static analysis",
        asset = "API Keys",
        likelihood = 5,
        impact = 4,
        mitigation = "Server-side key management, obfuscation"
    )
)

1.4.4 Risk Assessment Matrix

                    IMPACT
           Low    Medium    High    Critical
         ┌──────┬────────┬────────┬──────────┐
    High │  4   │   8    │   12   │    16    │  ← Address Immediately
         ├──────┼────────┼────────┼──────────┤
  Medium │  3   │   6    │    9   │    12    │  ← Plan Mitigation
L        ├──────┼────────┼────────┼──────────┤
I   Low  │  2   │   4    │    6   │     8    │  ← Monitor
K        ├──────┼────────┼────────┼──────────┤
E   Rare │  1   │   2    │    3   │     4    │  ← Accept Risk
L        └──────┴────────┴────────┴──────────┘
I
H
O
O
D

1.5 Secure Development Lifecycle

Security must be integrated throughout your development process, not bolted on at the end.

1.5.1 Security Gates

┌─────────────────────────────────────────────────────────────────┐
│                    SECURE DEVELOPMENT LIFECYCLE                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  DESIGN          BUILD           TEST            RELEASE         │
│    │               │               │                │            │
│    ▼               ▼               ▼                ▼            │
│ ┌──────┐       ┌──────┐       ┌──────┐        ┌──────┐         │
│ │Threat│       │Static│       │Dynamic│       │Final │          │
│ │Model │  →    │Scan  │  →    │Testing│  →    │Review│          │
│ └──────┘       └──────┘       └──────┘        └──────┘          │
│    │               │               │                │            │
│    ▼               ▼               ▼                ▼            │
│ Security       Dependency      Penetration     Security          │
│ Requirements   Analysis        Testing         Sign-off          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1.5.2 Security Requirements Checklist

Use this checklist at the start of every project:

## Security Requirements Checklist

### Data Protection
- [ ] Identify all sensitive data the app will handle
- [ ] Define encryption requirements for data at rest
- [ ] Define encryption requirements for data in transit
- [ ] Plan secure data deletion procedures

### Authentication & Authorization
- [ ] Define authentication mechanism (password, biometric, OAuth)
- [ ] Plan session management strategy
- [ ] Define authorization levels and access controls
- [ ] Plan account recovery procedures

### Network Security
- [ ] Require TLS for all network communication
- [ ] Plan certificate pinning strategy
- [ ] Define API security requirements
- [ ] Plan for network error handling

### Code Security
- [ ] Define obfuscation requirements
- [ ] Plan for secure API key management
- [ ] Define logging policy (no sensitive data)
- [ ] Plan anti-tampering measures

### Platform Security
- [ ] Define minimum Android version
- [ ] List required permissions with justification
- [ ] Define backup policy
- [ ] Plan root/emulator detection if needed

1.5.3 Continuous Security Integration

build.gradle configuration for security checks:

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
}

android {
    // ... standard configuration
    
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            
            // Security-focused build config
            buildConfigField("Boolean", "ENABLE_LOGGING", "false")
            buildConfigField("Boolean", "ENABLE_DEBUG_FEATURES", "false")
        }
        
        debug {
            buildConfigField("Boolean", "ENABLE_LOGGING", "true")
            buildConfigField("Boolean", "ENABLE_DEBUG_FEATURES", "true")
        }
    }
    
    // Lint checks for security issues
    lint {
        warningsAsErrors = true
        abortOnError = true
        
        // Enable security-related checks
        enable += setOf(
            "HardcodedDebugMode",
            "AllowBackup",
            "SetWorldReadable",
            "SetWorldWritable",
            "GrantAllUriPermissions",
            "WorldReadableFiles",
            "WorldWriteableFiles"
        )
    }
}

dependencies {
    // Security libraries
    implementation("androidx.security:security-crypto:1.1.0-alpha06")
    implementation("net.zetetic:android-database-sqlcipher:4.5.4")
    
    // Security testing
    testImplementation("org.owasp:dependency-check-gradle:8.4.0")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

Security-focused ProGuard rules:

# proguard-rules.pro

# Keep security-critical annotations
-keepattributes *Annotation*

# Obfuscate everything by default
-repackageclasses 'a'
-allowaccessmodification

# Remove logging in release
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
    public static int w(...);
    public static int e(...);
}

# Remove debug code
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}

# Protect sensitive classes from reflection attacks
-keepclassmembers class * {
    @androidx.annotation.Keep *;
}

1.6 Common Security Mistakes

Learning from common mistakes helps you avoid them in your own code.

1.6.1 Top 10 Android Security Mistakes

// ❌ MISTAKE 1: Logging sensitive data
Log.d(TAG, "User password: $password")
Log.d(TAG, "Auth token: $token")
Log.d(TAG, "API Response: $sensitiveResponse")

// ✅ FIX: Never log sensitive data
Log.d(TAG, "Login attempt for user: ${userId.hashCode()}")


// ❌ MISTAKE 2: Hardcoded secrets
const val API_KEY = "sk_live_abc123xyz789"
const val ENCRYPTION_KEY = "my_secret_key_123"

// ✅ FIX: Use secure key management
val apiKey = BuildConfig.API_KEY  // From local.properties (not committed)
val encryptionKey = keyStore.getKey("alias", null)


// ❌ MISTAKE 3: Allowing cleartext traffic
// Default in Android 8.1 and below - cleartext allowed

// ✅ FIX: Enforce TLS
// android:usesCleartextTraffic="false" in manifest
// Or use network_security_config.xml


// ❌ MISTAKE 4: Trusting all certificates
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
    override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
    override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
    override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})

// ✅ FIX: Use system trust store or certificate pinning


// ❌ MISTAKE 5: Exported components without protection
<activity android:name=".AdminActivity" android:exported="true" />

// ✅ FIX: Protect sensitive components
<activity 
    android:name=".AdminActivity" 
    android:exported="false" />
    
<activity 
    android:name=".DeepLinkActivity" 
    android:exported="true"
    android:permission="com.example.ADMIN_PERMISSION" />


// ❌ MISTAKE 6: SQL injection vulnerability
val query = "SELECT * FROM users WHERE name = '$userInput'"
database.rawQuery(query, null)

// ✅ FIX: Use parameterized queries
val query = "SELECT * FROM users WHERE name = ?"
database.rawQuery(query, arrayOf(userInput))


// ❌ MISTAKE 7: Storing passwords instead of hashes
sharedPrefs.putString("password", plainTextPassword)

// ✅ FIX: Never store passwords
// Use proper authentication (OAuth, tokens) or hash with bcrypt/Argon2


// ❌ MISTAKE 8: Using deprecated crypto
val cipher = Cipher.getInstance("DES/ECB/PKCS5Padding")  // Weak!
val md = MessageDigest.getInstance("MD5")  // Broken!
val md2 = MessageDigest.getInstance("SHA1")  // Weak!

// ✅ FIX: Use modern algorithms
val cipher = Cipher.getInstance("AES/GCM/NoPadding")  // Strong
val md = MessageDigest.getInstance("SHA-256")  // Secure


// ❌ MISTAKE 9: Ignoring backup security
<application android:allowBackup="true">  // Default!

// ✅ FIX: Disable or control backup
<application 
    android:allowBackup="false"
    android:fullBackupContent="@xml/backup_rules">


// ❌ MISTAKE 10: Implicit intents for sensitive data
val intent = Intent("com.example.SEND_DATA")
intent.putExtra("secret", sensitiveData)
sendBroadcast(intent)  // Any app can receive!

// ✅ FIX: Use explicit intents and local broadcasts
val intent = Intent(this, DataReceiver::class.java)
intent.putExtra("secret", sensitiveData)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)

1.6.2 Security Code Review Checklist

When reviewing code for security issues, check:

## Code Review Security Checklist

### Data Handling
- [ ] No sensitive data in logs
- [ ] No hardcoded secrets
- [ ] Sensitive data encrypted at rest
- [ ] Secure random number generation used
- [ ] Memory cleared after handling secrets

### Network
- [ ] TLS enforced for all connections
- [ ] Certificate pinning implemented
- [ ] No custom TrustManager that trusts all
- [ ] Proper error handling (no info leakage)

### Storage
- [ ] Sensitive data in encrypted storage
- [ ] Proper file permissions (MODE_PRIVATE)
- [ ] Backup rules configured
- [ ] No sensitive data on external storage

### Components
- [ ] No unnecessary exported components
- [ ] Intent data validated
- [ ] Deep links validated
- [ ] Content providers protected

### Cryptography
- [ ] Modern algorithms used (AES-GCM, SHA-256+)
- [ ] Keys stored in Android Keystore
- [ ] No hardcoded IVs or keys
- [ ] Proper key derivation for passwords

1.7 Summary

In this chapter, we established the foundation for Android security:

Android Security Architecture:

  • Multiple layers of defense from hardware to application
  • Linux kernel provides process isolation and SELinux
  • Hardware-backed security through TEE and StrongBox
  • Application sandbox isolates each app

Permission System:

  • Protection levels: normal, dangerous, signature, privileged
  • Runtime permissions for dangerous permissions
  • One-time permissions and auto-reset features
  • Principle of least privilege

Security Evolution:

  • Each Android version adds security improvements
  • Network Security Configuration for declarative security
  • Scoped storage for improved privacy
  • Always test on your minimum supported API level

Threat Modeling:

  • STRIDE framework for categorizing threats
  • Identify assets, threat actors, and attack vectors
  • Risk assessment prioritizes mitigation efforts
  • Document and review threat models regularly

Secure Development:

  • Integrate security throughout the development lifecycle
  • Use security gates at design, build, test, and release
  • Automate security checks in CI/CD
  • Learn from common security mistakes

The security mechanisms we discussed in this chapter protect your application at the system level. However, they are not sufficient on their own. In the next chapter, we'll implement secure data storage that protects user information even when these system-level protections are bypassed.


1.8 Key Takeaways

  1. Defense in Depth: Never rely on a single security mechanism. Layer multiple protections.

  2. Least Privilege: Request only the permissions you need, when you need them.

  3. Assume Breach: Design your app as if the device is already compromised.

  4. Stay Updated: Android security improves with each release. Support the latest versions.

  5. Think Like an Attacker: Threat modeling helps you find vulnerabilities before others do.


References