Mobile App Security
Secure mobile applications against common attack vectors. Covers certificate pinning, secure storage, biometric authentication, code obfuscation, anti-tampering, and the patterns that protect sensitive data on devices you don't control.
Mobile applications run on devices you do not control. Unlike servers behind firewalls, mobile apps can be decompiled, debugged, intercepted, and modified by anyone with a $50 Android phone and free tools. Mobile security is not about making attacks impossible — it is about making them expensive enough that attackers choose easier targets.
Threat Model
Mobile App Attack Surface:
Network Layer:
☐ Man-in-the-middle (intercept API traffic)
☐ SSL stripping (downgrade HTTPS to HTTP)
☐ DNS spoofing (redirect to malicious server)
Device Storage:
☐ Unencrypted local databases
☐ Tokens in SharedPreferences/UserDefaults (plaintext)
☐ Sensitive data in logs or screenshots
☐ Backup extraction (adb backup, iTunes backup)
Binary Analysis:
☐ Decompilation (jadx, Hopper)
☐ API keys hardcoded in binary
☐ Business logic reverse engineering
☐ Tampering (modify app, resign, distribute)
Runtime:
☐ Debugger attachment (Frida, LLDB)
☐ Method swizzling/hooking
☐ Jailbreak/root detection bypass
☐ Clipboard interception
Certificate Pinning
// iOS: Certificate pinning with URLSession
class PinnedSessionDelegate: NSObject, URLSessionDelegate {
// SHA256 hash of your server's certificate public key
let pinnedPublicKeyHash = "sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Extract public key and hash it
let publicKey = SecCertificateCopyKey(certificate)
let publicKeyHash = sha256Hash(of: publicKey)
if publicKeyHash == pinnedPublicKeyHash {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
// Pin mismatch — possible MITM attack
completionHandler(.cancelAuthenticationChallenge, nil)
reportPinningFailure()
}
}
}
Secure Storage
// Android: Secure storage with EncryptedSharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
class SecureStorage(context: Context) {
private val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
private val securePrefs = EncryptedSharedPreferences.create(
"secure_prefs",
masterKey,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveToken(token: String) {
securePrefs.edit().putString("auth_token", token).apply()
}
fun getToken(): String? {
return securePrefs.getString("auth_token", null)
}
// NEVER do this:
// SharedPreferences.edit().putString("token", token) // Plaintext!
// File("token.txt").writeText(token) // Plaintext file!
// Log.d("Auth", "Token: $token") // In logs!
}
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Hardcoded API keys in binary | Extracted in minutes with jadx | Server-side key management, short-lived tokens |
| Plaintext token storage | Token stolen from device backup | EncryptedSharedPreferences / Keychain |
| No certificate pinning | Traffic intercepted by proxy tools | Pin server certificate public key |
| Sensitive data in logs | Visible in logcat/Console | Strip logs in release builds |
| Trust jailbreak detection alone | Bypassed in minutes with Frida | Defense in depth, not single detection |
Mobile security is defense in depth. No single protection is unbreakable — but layering certificate pinning, secure storage, obfuscation, and anti-tampering makes attacks expensive enough to deter most adversaries.