ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Mobile App Security: Protecting Data on Untrusted Devices

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 security operates under a fundamentally different threat model than server security. On the server, you control the environment. On mobile, the device belongs to the user — or the attacker. The binary is downloadable, decompilable, and modifiable. The network is interceptable. The storage is accessible on rooted/jailbroken devices.

Mobile security is not about making attacks impossible. It is about making them expensive enough that attackers choose easier targets.


The Mobile Threat Model

What You Cannot Trust

  • The device: May be rooted/jailbroken, running a modified OS
  • The binary: Can be decompiled, patched, and redistributed
  • The network: Can be intercepted, even with HTTPS
  • Local storage: Accessible on rooted devices
  • The runtime: Debuggers can attach, memory can be read

What You Must Protect

  • User credentials: Tokens, passwords, biometric data
  • API keys: Service keys, analytics tokens, payment credentials
  • Business logic: Pricing algorithms, validation rules
  • User data: PII, financial data, health data

Secure Storage

iOS Keychain

The iOS Keychain is hardware-backed encrypted storage:

import Security

func saveToKeychain(key: String, data: Data) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    
    SecItemDelete(query as CFDictionary)  // Remove existing
    let status = SecItemAdd(query as CFDictionary, nil)
    return status == errSecSuccess
}

Key kSecAttrAccessible values:

  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly: Most secure, not backed up
  • kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: Available in background

Android Keystore

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

val keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
)

keyGenerator.init(
    KeyGenParameterSpec.Builder("secret_key",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setUserAuthenticationRequired(true)
        .setUserAuthenticationValidityDurationSeconds(300)
        .build()
)

What NOT to Store Securely

Never store on the device:

  • API secret keys (use a backend proxy)
  • Encryption keys for server-side data
  • Any credential that could be used to impersonate all users

Certificate Pinning

Standard HTTPS trusts any certificate signed by a trusted CA. Certificate pinning restricts trust to specific certificates or public keys:

iOS Implementation

// URLSession delegate for certificate pinning
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    
    guard let serverTrust = challenge.protectionSpace.serverTrust,
          let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    
    let serverPublicKey = SecCertificateCopyKey(serverCert)
    let pinnedPublicKey = loadPinnedPublicKey()
    
    if serverPublicKey == pinnedPublicKey {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

Pin Public Keys, Not Certificates

Certificates expire and rotate. Public keys persist across certificate renewals. Pin the public key hash:

Pin-SHA256: "base64encodedSHA256ofSubjectPublicKeyInfo"

Certificate Pinning Risks

  • App updates required: If you rotate keys without updating the pin set, the app breaks
  • Backup pins: Always include at least one backup pin
  • Emergency bypass: Have a mechanism to disable pinning remotely if a key rotation goes wrong

Biometric Authentication

// iOS: Face ID / Touch ID
let context = LAContext()
var error: NSError?

if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
                          localizedReason: "Authenticate to view your account") { success, error in
        if success {
            // Biometric authentication succeeded
            unlockKeychain()
        }
    }
}

Biometric Best Practices

  • Use biometrics to unlock a locally stored token, not as the sole authentication
  • Fall back to PIN/password, not to “no authentication”
  • Re-authenticate for sensitive actions (payment, data export)
  • Do not store the biometric data — use OS-level APIs that handle this securely

Runtime Integrity Checks

Root/Jailbreak Detection

// Android: Basic root detection
fun isDeviceRooted(): Boolean {
    val paths = arrayOf(
        "/system/app/Superuser.apk",
        "/system/xbin/su",
        "/system/bin/su",
        "/data/local/bin/su",
        "/data/local/xbin/su"
    )
    return paths.any { File(it).exists() } ||
           Build.TAGS?.contains("test-keys") == true
}

Important: Root detection is a speed bump, not a wall. Determined attackers bypass it. Use it as one signal among many, not as a single gate.

Tamper Detection

Verify the app binary has not been modified:

// iOS: App Store receipt validation
func verifyAppIntegrity() -> Bool {
    guard let receiptURL = Bundle.main.appStoreReceiptURL,
          FileManager.default.fileExists(atPath: receiptURL.path) else {
        return false
    }
    // Validate receipt with Apple's servers
    return validateReceipt(at: receiptURL)
}

API Security for Mobile

Token Management

Access Token:  Short-lived (15-60 minutes), stored in memory
Refresh Token: Long-lived (30-90 days), stored in Keychain/Keystore
API Key:       Embedded in binary (public, rate-limited, not a secret)

Request Signing

Prevent request tampering by signing API requests:

# HMAC-based request signing
import hmac
import hashlib

def sign_request(method, path, body, timestamp, secret):
    message = f"{method}\n{path}\n{timestamp}\n{hashlib.sha256(body).hexdigest()}"
    return hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()

Server-Side Enforcement

The server should never trust the client:

  • Validate all input server-side (prices, quantities, permissions)
  • Rate limit per user and per device
  • Log and alert on anomalous patterns (impossible travel, repeated failures)

Anti-Patterns

Anti-PatternRiskFix
Hardcoded API secretsExtracted from binary in minutesBackend proxy or token exchange
Trusting client-side validationBusiness logic bypassValidate everything server-side
No certificate pinningMan-in-the-middle attacksPin public keys with backup pins
Root detection as sole defenseBypassed by frida/xposedLayer multiple detection signals
Storing tokens in UserDefaults/SharedPreferencesReadable on compromised devicesKeychain/Keystore only

Mobile security is defense in depth. No single technique stops a determined attacker. The combination of secure storage, certificate pinning, integrity checks, server-side validation, and monitoring makes attacks expensive enough to deter all but the most motivated adversaries.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →