Skip to main content

Handling Application Security

1. Encrypting Data with KeyStore

The Android Keystore system provides a secure way to store cryptographic keys and sensitive data. It ensures that keys are never exposed in plaintext, even if the application is compromised.

Property Description Notes
KeyStore Android's secure storage system for cryptographic keys Keys are stored in a secure hardware-backed environment when available
KeyGenerator Class used to generate keys for encryption Must be initialized with a KeyStore instance and key alias
Cipher Class used to perform encryption and decryption operations Uses the keys stored in the Keystore for cryptographic operations

Example: Generate a Key in Keystore

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore

val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)

val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
    "my_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_CBC)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
    .build()

keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()

Example: Encrypt Data with Keystore Key

import javax.crypto.Cipher
import android.security.keystore.KeyProperties

val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" +
    KeyProperties.BLOCK_MODE_CBC + "/" +
    KeyProperties.ENCRYPTION_PADDING_PKCS7)

val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKey = keyStore.getKey("my_key", null) as SecretKey

cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val encryptedData = cipher.doFinal("Sensitive data".toByteArray())

Example: Decrypt Data with Keystore Key

import javax.crypto.Cipher
import android.security.keystore.KeyProperties

val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" +
    KeyProperties.BLOCK_MODE_CBC + "/" +
    KeyProperties.ENCRYPTION_PADDING_PKCS7)

val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKey = keyStore.getKey("my_key", null) as SecretKey

cipher.init(Cipher.DECRYPT_MODE, secretKey)
val decryptedData = cipher.doFinal(encryptedData)
Note: Keystore keys are only accessible by the app that created them. They are protected by hardware security modules on devices that support it.

2. Using BiometricPrompt

The BiometricPrompt API in Android provides a standardized way to handle biometric authentication (fingerprint, face, iris, etc.) in Kotlin applications. It offers a consistent user experience and integrates well with Android's security model.

Property Description Values
BiometricPrompt.Builder Used to configure and build a BiometricPrompt Instance of BiometricPrompt.Builder
setNegativeButton Configures the negative button (e.g., "Cancel") CharSequence, Executor, DialogInterface.OnClickListener
setConfirmationRequired Controls whether confirmation is required after biometric authentication Boolean
setDeviceCredentialAllowed Allows fallback to device credentials (e.g., PIN, pattern) Boolean

Example: Initializing BiometricPrompt

import android.security.biometrics.BiometricPrompt
import android.security.biometrics.BiometricManager
import androidx.fragment.app.FragmentActivity

class BiometricAuthFragment : FragmentActivity() {
    private lateinit var biometricPrompt: BiometricPrompt
    private lateinit var promptInfo: BiometricPrompt.PromptInfo

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        biometricPrompt = BiometricPrompt(this, object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                // Handle successful authentication
            }

            override fun onAuthenticationFailed() {
                // Handle failed authentication
            }
        })

        promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate")
            .setSubtitle("Use fingerprint to proceed")
            .setDescription("Touch sensor to authenticate")
            .setConfirmationRequired(true)
            .setDeviceCredentialAllowed(true)
            .build()
    }

    fun authenticate() {
        biometricPrompt.authenticate(promptInfo)
    }
}
Note: The BiometricPrompt requires the USE_BIOMETRIC permission in the manifest.
Warning: Ensure that your app handles authentication failures gracefully and provides appropriate fallback mechanisms (e.g., PIN or password).
Feature Support Status Notes
Fingerprint Authentication Supported Available on Android 6.0+
Face Recognition Supported Available on Android 10+
Iris Scanning Limited Not widely supported on Android devices
Device Credential Fallback Supported Allows fallback to PIN or pattern

3. Implementing Certificate Pinning

Certificate pinning is a security technique that ensures your app only accepts connections to specific certificates or public keys, mitigating man-in-the-middle (MITM) attacks.

Example: Using OkHttp with Certificate Pinning

import okhttp3.*
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.io.InputStream

class PinningInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)

        // Validate certificate against pinned values
        if (!isCertificateValid(response)) {
            throw IOException("Certificate pinning failed")
        }

        return response
    }

    private fun isCertificateValid(response: Response): Boolean {
        val certificates = response.handshake?.peerCertificates ?: return false
        // Compare with pinned certificates (simplified)
        return true
    }
}

// Usage in OkHttpClient
val client = OkHttpClient.Builder()
    .addInterceptor(PinningInterceptor())
    .build()
Property Description Values
Certificate Pinning Ensures only trusted certificates are accepted Enabled / Disabled
Certificate Hash SHA-256 hash of the pinned certificate sha256/...
Certificate Chain Full certificate chain to validate List of certificates
Note: Certificate pinning should be used in conjunction with other security measures like secure network communication and proper key management.

4. Validating Input Data

Example: Basic Input Validation

fun validateEmail(email: String): Boolean {
    val emailRegex = Regex("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$")
    return emailRegex.matches(email)
}

fun validatePhone(phone: String): Boolean {
    val phoneRegex = Regex("^\\+?[1-9]\\d{1,14}$")
    return phoneRegex.matches(phone)
}
Validation Type Method Description
Email Validation Regex("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$") Checks for valid email format
Phone Number Validation Regex("^\\+?[1-9]\\d{1,14}$") Validates E.164 format phone numbers
Length Validation input.length <= maxLen Ensures input does not exceed maximum length
Note: Always validate input on both client and server side to prevent security vulnerabilities.
Warning: Never trust user input. Always sanitize and validate before processing.

Example: Sanitizing User Input

fun sanitizeInput(input: String): String {
    return input
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace("&", "&amp;")
        .replace("\"", "&quot;")
}

5. Securing SharedPreferences

Property Description Security Level Notes
MODE_PRIVATE Default mode; data is only accessible by the app High Recommended for most use cases
MODE_WORLD_READABLE Allows other apps to read the file Low DEPRECATED in API 17+
MODE_WORLD_WRITEABLE Allows other apps to write to the file Low DEPRECATED in API 17+

Example: Secure SharedPreferences Usage

val prefs = getSharedPreferences("secure_prefs", Context.MODE_PRIVATE)
val editor = prefs.edit()
editor.putString("user_token", "abc123xyz")
editor.apply()
Note: Never store sensitive data like passwords or tokens in SharedPreferences without encryption.
Warning: Using MODE_WORLD_READABLE or MODE_WORLD_WRITEABLE exposes data to other apps.
Encryption Method Security Level Implementation
AES-256 High Use AndroidKeyStore with Cipher
Base64 Encoding Medium Simple encoding, not encryption
No Encryption Low Data stored in plain text

6. Using HTTPS Only

Property Description Notes
networkSecurityConfig Reference to a network security configuration XML file Used to define security policies for the app
cleartextTrafficPermitted Boolean flag to allow cleartext (HTTP) traffic Set to false to enforce HTTPS only

Example: Enforcing HTTPS Only

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/trusted_certs"/>
        </trust-anchors>
    </base-config>
</network-security-config>

Example: Applying Network Security Config

<application
    android:name=".MyApplication"
    android:networkSecurityConfig="@xml/network_security_config"
    ... >
</application>
Note: Setting cleartextTrafficPermitted to false blocks all HTTP traffic and enforces HTTPS.
Warning: Disabling cleartext traffic may break apps that rely on HTTP endpoints.

HTTPS Enforcement Summary

  • All network requests must use HTTPS
  • HTTP traffic is blocked by default
  • Network security config allows fine-grained control

7. Implementing App Signing

Aspect Description Details
Key Store Storage for signing keys Contains private key and certificate chain to sign the app
Key Alias Identifier for the key Unique name within the keystore to reference the signing key
Key Password Password for the key Protects access to the private key in the keystore
Keystore Password Password for the keystore Protects access to the entire keystore file

Example: Generate Keystore Using Keytool

keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

Example: Configure Gradle for Release Build

android {
    signingConfigs {
        release {
            storeFile file('my-release-key.keystore')
            storePassword 'password'
            keyAlias 'my-key-alias'
            keyPassword 'password'
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
Note: Store the keystore file securely and never commit it to version control.
Warning: If the keystore is lost or compromised, you cannot update the app on the Play Store.

8. Protecting Against SQL Injection

SQL injection is a common vulnerability where malicious input is used to manipulate SQL queries. In Android-Kotlin, it's crucial to use parameterized queries and avoid string concatenation in SQL statements.

Technique Description Example
Parameterized Queries Use parameterized queries to prevent SQL injection by separating SQL logic from data.
val query = "SELECT * FROM users WHERE name = ? AND age = ?"
val cursor = db.rawQuery(query, arrayOf(userName, userAge))
Room Database Room ORM provides compile-time SQL validation and parameter binding to prevent injection.
@Query("SELECT * FROM users WHERE name = :name AND age = :age")
fun getUsersByNameAndAge(name: String, age: Int): List<User>
Raw SQL with Caution When using raw SQL, always sanitize and validate input before constructing queries.
val sanitizedInput = userInput.replace("'", "''")
val query = "SELECT * FROM users WHERE name = '$sanitizedInput'"

Example: Room Query with Parameters

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE email = :email")
    fun findUserByEmail(email: String): User?
}
Note: Never concatenate user input directly into SQL strings. Always use parameterized queries or ORM methods like Room to handle data binding safely.

9. Using R8 for Obfuscation

R8 is the default code shrinker, optimizer, and obfuscator for Android apps. It helps reduce APK size and makes reverse engineering more difficult by renaming classes, methods, and fields to shorter, less meaningful names.

Feature Description Notes
Proguard/R8 Rules Configuration files that define which code should not be obfuscated or removed. Use proguard-rules.pro or consumer-proguard-rules.pro
Shrinking Removes unused code from the app. Enabled by default in release builds
Optimization Improves code execution performance. Enabled by default in release builds
Obfuscation Renames classes, methods, and fields to shorter names. Enabled by default in release builds

Example: Basic R8 Configuration

# Keep the main application class
-keep public class com.example.MyApplication {
    public static void main(java.lang.String[]);
}

# Keep all public classes and methods in a specific package
-keep public class com.example.api.** {
    public <methods>;
}

# Do not obfuscate specific classes
-keepnames class com.example.MyClass

# Keep annotations
-keepattributes *Annotation*
Note: When using R8, ensure that any reflection or dynamic code access is properly configured to prevent runtime errors due to obfuscation.
Warning: Incorrect ProGuard/R8 rules can lead to runtime exceptions or unexpected behavior in your app.

10. Securing API Keys

Approach Description Implementation Notes
Build-time Injection Inject API keys during build process using Gradle properties or build variants.
build.gradle.kts
android {
    buildTypes {
        release {
            buildConfigField("String", "API_KEY", "\"<your_api_key>\"")
        }
    }
}
Keys are embedded in the APK but not readable in source code.
String Resources Store keys in res/values/strings.xml and access via getString(). <string name="api_key"><your_api_key></string> Not secure for production; can be extracted from APK.
Environment Variables Use environment variables during development and CI/CD pipeline.
build.gradle.kts
val apiKey = System.getenv("API_KEY") ?: "default_key"
buildConfigField("String", "API_KEY", "\"$apiKey\"")
Secure in CI but not in app binary.
Secure Storage (Keystore) Use Android Keystore to store sensitive keys securely.
Kotlin
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKeyEntry = keyStore.getEntry("api_key", null) as KeyStore.SecretKeyEntry
Recommended for sensitive keys in production apps.

Example: Using BuildConfig for API Key

class ApiClient {
    companion object {
        const val API_KEY = BuildConfig.API_KEY
    }
}
Note: Never hardcode API keys in version-controlled repositories.
Warning: Even with build-time injection, keys in APKs can be extracted by reverse engineering tools.

Was this article helpful?