← Back to Guides

Mobile App Security Best Practices

📖 14 min read | 📅 Updated: January 2025 | 🏷️ Mobile Development

Introduction

Mobile app security is critical for protecting user data and maintaining trust. This comprehensive guide covers encryption, secure storage, API security, and common vulnerability prevention for iOS and Android applications.

1. Data Encryption

Encrypting Data at Rest

// iOS - Using Keychain for sensitive data
import Security

class KeychainManager {
    static func save(key: String, data: Data) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data
        ]
        
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }
    
    static func load(key: String) -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true
        ]
        
        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        return status == errSecSuccess ? result as? Data : nil
    }
}

Android Encrypted SharedPreferences

// Android - Using EncryptedSharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

class SecureStorage(context: Context) {
    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()
    
    private val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "secure_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    
    fun saveString(key: String, value: String) {
        sharedPreferences.edit().putString(key, value).apply()
    }
    
    fun getString(key: String): String? {
        return sharedPreferences.getString(key, null)
    }
}

2. Secure Network Communication

Certificate Pinning

// iOS - SSL Pinning with URLSession
class NetworkManager: NSObject, URLSessionDelegate {
    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
        }
        
        let serverCertificateData = SecCertificateCopyData(certificate) as Data
        let pinnedCertificateData = // Load your pinned certificate
        
        if serverCertificateData == pinnedCertificateData {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}

Android Network Security Config




    
        api.yourdomain.com
        
            base64encodedpin==
            backuppin==
        
    




3. Authentication & Authorization

Biometric Authentication

// iOS - Face ID / Touch ID
import LocalAuthentication

class BiometricAuth {
    func authenticate(completion: @escaping (Bool, Error?) -> Void) {
        let context = LAContext()
        var error: NSError?
        
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            context.evaluatePolicy(
                .deviceOwnerAuthenticationWithBiometrics,
                localizedReason: "Authenticate to access your account"
            ) { success, error in
                completion(success, error)
            }
        } else {
            completion(false, error)
        }
    }
}

Secure Token Storage

// React Native - Secure token management
import * as SecureStore from 'expo-secure-store';

class AuthService {
    static async saveAuthToken(token: string): Promise {
        try {
            await SecureStore.setItemAsync('auth_token', token);
        } catch (error) {
            console.error('Failed to save token:', error);
        }
    }
    
    static async getAuthToken(): Promise {
        try {
            return await SecureStore.getItemAsync('auth_token');
        } catch (error) {
            console.error('Failed to retrieve token:', error);
            return null;
        }
    }
    
    static async deleteAuthToken(): Promise {
        try {
            await SecureStore.deleteItemAsync('auth_token');
        } catch (error) {
            console.error('Failed to delete token:', error);
        }
    }
}

4. Input Validation & Sanitization

Preventing Injection Attacks

// Input validation example
class InputValidator {
    static func validateEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }
    
    static func sanitizeInput(_ input: String) -> String {
        return input
            .replacingOccurrences(of: "<", with: "<")
            .replacingOccurrences(of: ">", with: ">")
            .replacingOccurrences(of: "&", with: "&")
            .replacingOccurrences(of: "\"", with: """)
            .replacingOccurrences(of: "'", with: "'")
    }
    
    static func validateLength(_ input: String, min: Int, max: Int) -> Bool {
        return input.count >= min && input.count <= max
    }
}

5. Code Obfuscation

ProGuard Configuration (Android)

# proguard-rules.pro
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

# Obfuscate code
-repackageclasses
-allowaccessmodification

# Keep specific classes
-keep class com.yourapp.models.** { *; }

# Remove logging in release
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
}

6. Secure API Communication

API Request Signing

// API request with HMAC signature
import CryptoKit

class APIClient {
    private let apiSecret = "your_secret_key"
    
    func signRequest(payload: String, timestamp: String) -> String {
        let message = "\(payload)\(timestamp)"
        let key = SymmetricKey(data: apiSecret.data(using: .utf8)!)
        let signature = HMAC.authenticationCode(
            for: message.data(using: .utf8)!,
            using: key
        )
        return Data(signature).base64EncodedString()
    }
    
    func makeSecureRequest(url: URL, payload: [String: Any]) async throws -> Data {
        var request = URLRequest(url: url)
        let timestamp = String(Int(Date().timeIntervalSince1970))
        let payloadString = try JSONSerialization.data(
            withJSONObject: payload
        ).base64EncodedString()
        
        let signature = signRequest(payload: payloadString, timestamp: timestamp)
        
        request.addValue(signature, forHTTPHeaderField: "X-Signature")
        request.addValue(timestamp, forHTTPHeaderField: "X-Timestamp")
        request.httpMethod = "POST"
        request.httpBody = try JSONSerialization.data(withJSONObject: payload)
        
        let (data, _) = try await URLSession.shared.data(for: request)
        return data
    }
}

7. Jailbreak & Root Detection

iOS Jailbreak Detection

class JailbreakDetector {
    static func isJailbroken() -> Bool {
        // Check for common jailbreak files
        let paths = [
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/etc/apt",
            "/private/var/lib/apt/"
        ]
        
        for path in paths {
            if FileManager.default.fileExists(atPath: path) {
                return true
            }
        }
        
        // Check if can write to system
        let testPath = "/private/test_jailbreak.txt"
        do {
            try "test".write(toFile: testPath, atomically: true, encoding: .utf8)
            try FileManager.default.removeItem(atPath: testPath)
            return true
        } catch {
            return false
        }
    }
}

Android Root Detection

class RootDetector {
    fun isRooted(context: Context): Boolean {
        return checkRootFiles() || checkSuBinary() || checkRootApps(context)
    }
    
    private fun checkRootFiles(): Boolean {
        val paths = arrayOf(
            "/system/app/Superuser.apk",
            "/sbin/su",
            "/system/bin/su",
            "/system/xbin/su",
            "/data/local/xbin/su",
            "/data/local/bin/su"
        )
        
        return paths.any { File(it).exists() }
    }
    
    private fun checkSuBinary(): Boolean {
        return try {
            Runtime.getRuntime().exec("su")
            true
        } catch (e: Exception) {
            false
        }
    }
    
    private fun checkRootApps(context: Context): Boolean {
        val packages = arrayOf(
            "com.noshufou.android.su",
            "com.thirdparty.superuser",
            "eu.chainfire.supersu"
        )
        
        val pm = context.packageManager
        return packages.any { packageName ->
            try {
                pm.getPackageInfo(packageName, 0)
                true
            } catch (e: Exception) {
                false
            }
        }
    }
}

8. Secure Data Deletion

Proper Data Cleanup

// Secure deletion of sensitive data
class SecureDataManager {
    static func secureDelete(data: inout Data) {
        data.withUnsafeMutableBytes { bytes in
            memset(bytes.baseAddress, 0, data.count)
        }
        data.removeAll()
    }
    
    static func secureDeleteString(string: inout String) {
        var data = string.data(using: .utf8)!
        secureDelete(data: &data)
        string = ""
    }
    
    static func clearUserSession() {
        // Clear all user data
        UserDefaults.standard.removePersistentDomain(
            forName: Bundle.main.bundleIdentifier!
        )
        
        // Clear keychain
        let secItemClasses = [
            kSecClassGenericPassword,
            kSecClassInternetPassword,
            kSecClassCertificate,
            kSecClassKey,
            kSecClassIdentity
        ]
        
        for itemClass in secItemClasses {
            let spec: [String: Any] = [kSecClass as String: itemClass]
            SecItemDelete(spec as CFDictionary)
        }
    }
}

9. Runtime Application Self-Protection (RASP)

Detecting Debugging & Tampering

// Anti-debugging techniques
class SecurityMonitor {
    static func isBeingDebugged() -> Bool {
        var info = kinfo_proc()
        var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
        var size = MemoryLayout.stride
        
        let result = sysctl(&mib, 4, &info, &size, nil, 0)
        return result == 0 && (info.kp_proc.p_flag & P_TRACED) != 0
    }
    
    static func checkIntegrity() -> Bool {
        guard let executablePath = Bundle.main.executablePath else {
            return false
        }
        
        let fileManager = FileManager.default
        do {
            let attributes = try fileManager.attributesOfItem(atPath: executablePath)
            let modificationDate = attributes[.modificationDate] as? Date
            let creationDate = attributes[.creationDate] as? Date
            
            // Check if modification date is after creation
            if let mod = modificationDate, let create = creationDate {
                return mod <= create
            }
        } catch {
            return false
        }
        
        return true
    }
}

10. Security Best Practices Checklist

✓ Essential Security Measures:

Conclusion

Mobile app security requires a multi-layered approach covering data encryption, secure communication, authentication, and protection against common attacks. Implement these practices from the start of development and maintain them throughout your app's lifecycle. Regular security audits and staying updated with the latest security threats are essential for protecting your users and maintaining trust.

💡 Pro Tip: Security is not a one-time implementation but an ongoing process. Stay informed about new vulnerabilities, regularly update your security measures, and consider hiring security professionals for comprehensive audits of critical applications.