Mobile App Security Best Practices
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:
- Use HTTPS for all network communication
- Implement certificate pinning for critical APIs
- Store sensitive data in Keychain (iOS) or EncryptedSharedPreferences (Android)
- Never hardcode API keys or secrets in code
- Implement proper session management and timeout
- Use biometric authentication when available
- Validate and sanitize all user inputs
- Enable code obfuscation for production builds
- Implement jailbreak/root detection
- Use secure random number generators
- Implement proper error handling without exposing sensitive info
- Regular security audits and penetration testing
- Keep dependencies updated and scan for vulnerabilities
- Implement proper logging without sensitive data
- Use App Transport Security (iOS) and Network Security Config (Android)
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.