Implement mobile security that protects user data, API keys, and business logic on devices you do not control. Covers secure storage, certificate pinning, code obfuscation, biometric authentication, runtime integrity checks, and the threat model that makes mobile fundamentally different from server security.
Mobile apps run on untrusted devices: users own their phones, laptops, and tablets, and they may install apps from unknown sources, connect to public Wi-Fi, or sync with multiple accounts. Security is not an afterthought but a continuous process, from initial app installation to data at rest and in transit. This reference page details how to protect app data on such devices, focusing on what engineers must do to harden mobile apps against common attack vectors.
Data transmitted between app and backend must resist eavesdropping, tampering, and replay attacks. The foundation is TLS, but mobile apps must go beyond default configurations.
Mobile apps should use TLS 1.2 or higher with perfect forward secrecy (PFS) and certificate pinning. The app must validate server certificates against a known public key or hash.
{
"tls": {
"minimum_version": "1.2",
"supported_ciphers": [
"TLS_AES_256_GCM_SHA512",
"TLS_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA512",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
],
"certificate_pinning": {
"public_keys": [
"sha256:47:18:5c:3d:98:fb:12:3a:8e:01:45:8e:2a:9d:7e:96:21:16:05:8b:0c:0e:55:11:1b:1d:9e:4c:50:9c:85:4b",
"sha256:32:5a:12:7e:9c:1d:8a:4b:17:2c:54:91:4c:10:2d:1e:74:33:5b:16:44:91:14:8d:6f:28:02:12:13:7b:8c:31"
],
"include_subdomains": true,
"fail_on_missing_pinning": true
}
}
}
When the server certificate does not match the pinned public keys, the app must either fail the connection or fall back to a trusted root CA with a warning. The app logs TLS_PINNING_MISMATCH and TLS_PINNING_FALLBACK events.
Users may install custom root certificates (e.g., via corporate PKI or enterprise MDM) that trust a server’s certificate. The app must detect and validate that the trusted certificate chain includes at least one of the pinned certificates.
// Swift example: verify certificate chain includes a pinned cert
func validateCertificateChain(_ chain: [SecCertificate], pinnedPublicKeys: [Data]) -> Bool {
for cert in chain {
let certData = SecCertificateCopyExternalRepresentation(cert, nil) as Data?
guard let certData = certData else { continue }
let certHash = SHA256.hash(data: certData)
if pinnedPublicKeys.contains(where: { $0 == certHash }) {
return true
}
}
return false
}
If the chain contains a pinned certificate but not one of the expected public keys, the app logs CERTIFICATE_CHAIN_MISSING_PINNED_CERT and triggers a background check.
Data stored on the device—locally in SQLite, SharedPreferences, or files—must be protected against extraction, tampering, and theft.
Use the platform’s native encryption APIs to encrypt the app’s data directory. On iOS, use NSFileProtectionComplete with NSFileProtectionCompleteUntilFirstUserAuthentication. On Android, use EncryptedSharedPreferences with EncryptedFile and MasterKeys.
// Android: configure encrypted shared preferences
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.setKeyGenerator(MasterKey.KeyGenerator.SHA256)
.build();
EncryptedSharedPreferences.Builder builder = new EncryptedSharedPreferences.Builder(
context,
"app_prefs",
masterKey,
EncryptedSharedPreferences.AES_GCM_BLOCK_MODE,
true
);
SharedPreferences prefs = builder.build();
prefs.edit().putString("user_token", "abc123").apply();
The app must encrypt all database files, including SQLiteOpenHelper databases and Room persistent storage.
// Kotlin: encrypt Room database
val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.setJournalMode(JournalMode.WRITE_AHEAD_LOGGING)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
db.execSQL("PRAGMA key = 'secret_passphrase_123'")
db.execSQL("PRAGMA cipher_page_size = 4096")
db.execSQL("PRAGMA kdf_iter = 4000")
db.execSQL("PRAGMA cipher_use_hmac = 1")
db.execSQL("PRAGMA cipher_hmac = 1")
}
})
.build()
The database uses SQLite’s cipher extension with AES-256-GCM. The app must store the encryption key in the keystore and use a dynamic key derivation function (PBKDF2 or scrypt) to derive the database key from the master key.
The app must store secrets—such as API tokens, user passwords, and encryption keys—in the platform’s secure key storage.
On iOS, use SecKeyCreateWithData and SecKeyCopyAttributes to store and retrieve keys.
// Swift: store and retrieve a user session token
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
let tokenData = token.data(using: .utf8)!
let query: [CFString: Any] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrLabel: "UserSessionToken",
kSecAttrApplicationTag: "com.example.app.session_token".data(using: .utf8),
kSecValueData: tokenData
]
SecItemAdd(query as CFDictionary, nil)
let result = SecItemCopyMatching([
kSecClass: kSecClassKey,
kSecAttrLabel: "UserSessionToken"
] as CFDictionary, nil)
if let keyData = result as? Data, let tokenString = String(data: keyData, encoding: .utf8) {
print("Retrieved token: \(tokenString)")
}
On Android, use KeyStore with AndroidKeyStore and KeyGenerator.
// Android: generate and retrieve a key for encrypting user data
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES", "AndroidKeyStore");
keyGenerator.init(new KeyGenParameterSpec.Builder("user_data_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(300)
.setKeySize(256)
.build());
keyGenerator.generateKey();
SecretKey key = keyStore.getKey("user_data_key", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
The app must check if the key is available and prompt the user to re-authenticate if the key is locked (e.g., after device reboot or sleep).
The app must protect its own code and execution environment against tampering, dynamic analysis, and debugging.
The app must verify its integrity at startup and during execution. This includes checking the app binary, loaded libraries, and runtime state.
// C: verify app binary integrity using SHA-256
int verify_app_binary(const char* app_path, const char* expected_hash) {
FILE* file = fopen(app_path, "rb");
if (!file) return 0;
unsigned char hash[32];
SHA256_CTX ctx;
SHA256_Init(&ctx);
unsigned char buffer[4096];
size_t len;
while ((len = fread(buffer, 1, sizeof(buffer), file)) > 0) {
SHA256_Update(&ctx, buffer, len);
}
SHA256_Final(hash, &ctx);
fclose(file);
return memcmp(hash, expected_hash, 32) == 0;
}
On Android, use ApplicationInfo to read the android:extractNativeLibs and android:fullBackupContent flags and validate the APK’s classes.dex and lib/ folders.
The app must also detect common tampering tools:
frida-server process, frida-agent library, and frida-trace binary.lldb process and com.apple.debugserver running.adb daemon and adb connect command.When tampering is detected, the app logs RUNTIME_TAMPERING_DETECTED and can trigger actions like:
The app must ensure that the runtime environment is trusted.
{
"runtime": {
"trusted_environments": [
"device_is_jailbroken",
"app_is_debuggable",
"device_is_emulator",
"network_is_public",
"background_sync_enabled"
],
"checks": {
"device_is_jailbroken": {
"method": "file_exists",
"path": "/private/var/lib/apt",
"expected": true
},
"app_is_debuggable": {
"method": "android_manifest",
"key": "android:debuggable",
"expected": true
},
"device_is_emulator": {
"method": "property_exists",
"property": "ro.build.fingerprint",
"values": [
"generic_x86",
"google/sdk_google_phone_x86",
"samsung/klteatt/klte:5.0.1/LRX21M/1718933:userdebug/test-keys"
]
},
"network_is_public": {
"method": "dhcp_config",
"check": [
"gateway",
"dns"
],
"expected": {
"gateway": "192.168.1.1",
"dns": [
"8.8.8.8",
"1.1.1.1"
]
}
}
},
"actions": {
"on_tampering": [
"invalidate_session_tokens",
"enable_biometric_challenge",
"enable_hardened_mode"
]
}
}
}
When any check fails, the app updates its runtime_state and records the failure reason in the logs. The app can re-check these conditions periodically in the background.
When a security failure occurs, the app must not only detect it but also respond in a predictable way.
The app logs security events in a structured format using a standard schema.
{
"event": "TLS_PINNING_MISMATCH",
"timestamp": "2023-10-05T12:34:56.789Z",
"details": {
"expected_pinned_keys": [
"sha256:47:18:5c:3d:98:fb:12:3a:8e:01:45:8e:2a:9d:7e:96:21:16:05:8b:0c:0e:55:11:1b:1d:9e:4c:50:9c:85:4b",
"sha256:32:5a:12:7e:9c:1d:8a:4b:17:2c:54:91:4c:10:2d:1e:74:33:5b:16:44:91:14:8d:6f:28:02:12:13:7b:8c:31"
],
"actual_certificate_chain": [
{
"subject": "CN=api.example.com",
"issuer": "CN=Let's Encrypt Authority X3",
"public_key": "sha256:47:18:5c:3d:98:fb:12:3a:8e:01:45:8e:2a:9d:7e:96:21:16:05:8b:0c:0e:55:11:1b:1d:9e:4c:50:9c:85:4b"
},
{
"subject": "CN=Let's Encrypt Authority X3",
"issuer": "CN=ISRG Root X1",
"public_key": "sha256:32:5a:12:7e:9c:1d:8a:4b:17:2c:54:91:4c:10:2d:1e:74:33:5b:16:44:91:14:8d:6f:28:02:12:13:7b:8c:31"
}
],
"server_name": "api.example.com",
"network_type": "wifi",
"app_version": "1.4.2"
}
}
The app uploads these events to a central logging service via a background HTTP request.
When the app detects a critical failure—such as missing certificate pinning, tampered binary, or failed key retrieval—it must act immediately.
The app must be resilient to network loss, battery drain, and user interruptions during these responses.
The app must support key rotation for encryption and authentication.
When a key is rotated (e.g., due to compromise), the app updates the master_key_version and encryption_key_version fields in its configuration.
{
"keys": {
"master_key_version": 3,
"encryption_key_version": 2,
"rotation": {
"current": {
"version": 2,
"created_at": "2023-10-04T08:00:00Z",
"key": "1234567890abcdef1234567890abcdef"
},
"previous": [
{
"version": 1,
"created_at": "2023-09-25T10:15:30Z",
"key": "fedcba9876543210fedcba9876543210"
}
]
}
}
}
The app can decrypt data with any key in the previous list, allowing smooth transition during rotation. The app logs KEY_ROTATION_SUCCESS and KEY_ROTATION_FAILED events.
These patterns ensure that mobile apps remain secure even when deployed on devices beyond the control of the app’s developers.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy