Security is paramount in iOS app development. Learn to implement encryption, secure storage, and protect user data effectively.
Keychain Services
Store Sensitive Data
swiftimport Security import Foundation class KeychainManager { enum KeychainError: Error { case duplicateEntry case unknown(OSStatus) } func store(key: String, data: Data) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked ] let status = SecItemAdd(query as CFDictionary, nil) if status == errSecDuplicateItem { throw KeychainError.duplicateEntry } else if status != errSecSuccess { throw KeychainError.unknown(status) } } func retrieve(key: String) throws -> Data? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { return nil } return data } func delete(key: String) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key ] let status = SecItemDelete(query as CFDictionary) if status != errSecSuccess && status != errSecItemNotFound { throw KeychainError.unknown(status) } } }
Data Encryption
Encrypt with CryptoKit
swiftimport CryptoKit class EncryptionManager { private let key: SymmetricKey init() { // Generate or load key self.key = SymmetricKey(size: .bits256) } func encrypt(data: Data) throws -> Data { let sealedBox = try AES.GCM.seal(data, using: key) return sealedBox.combined ?? Data() } func decrypt(combined: Data) throws -> Data { let sealedBox = try AES.GCM.SealedBox(combined: combined) return try AES.GCM.open(sealedBox, using: key) } func encryptString(_ string: String) throws -> String { guard let data = string.data(using: .utf8) else { return "" } let encryptedData = try encrypt(data: data) return encryptedData.base64EncodedString() } func decryptString(_ encryptedString: String) throws -> String { guard let combined = Data(base64Encoded: encryptedString) else { return "" } let decryptedData = try decrypt(combined: combined) return String(data: decryptedData, encoding: .utf8) ?? "" } }
Secure Data Storage
UserDefaults Encryption
swiftclass SecureUserDefaults { private let keyPrefix = "secure_" private let encryptionManager = EncryptionManager() func set(_ value: String, forKey key: String) throws { let encrypted = try encryptionManager.encryptString(value) UserDefaults.standard.set(encrypted, forKey: keyPrefix + key) } func get(forKey key: String) throws -> String? { guard let encrypted = UserDefaults.standard.string(forKey: keyPrefix + key) else { return nil } return try encryptionManager.decryptString(encrypted) } }
Network Security
Certificate Pinning
swiftimport Foundation class SecureURLSessionDelegate: NSObject, URLSessionDelegate { func urlSession( _ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) { guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.performDefaultHandling, nil) return } // Validate certificate let credential = URLCredential(trust: serverTrust) if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { completionHandler(.useCredential, credential) } else { completionHandler(.performDefaultHandling, nil) } } }
SSL Pinning
swiftclass CertificatePinner { func validateCertificate(for url: URL) -> Bool { guard let certificateData = getCertificateData() else { return false } // Compare with server certificate return true } private func getCertificateData() -> Data? { guard let certPath = Bundle.main.path(forResource: "certificate", ofType: "cer") else { return nil } return try? Data(contentsOf: URL(fileURLWithPath: certPath)) } }
Biometric Authentication
Face ID / Touch ID
swiftimport LocalAuthentication class BiometricAuthManager: ObservableObject { @Published var isAuthenticated = false @Published var errorMessage: String? private let context = LAContext() func authenticate() { var error: NSError? if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { let reason = "Authenticate to access secure data" context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in DispatchQueue.main.async { if success { self.isAuthenticated = true } else { self.errorMessage = error?.localizedDescription } } } } else { errorMessage = "Biometric authentication not available" } } func getBiometricType() -> String { switch context.biometryType { case .faceID: return "Face ID" case .touchID: return "Touch ID" default: return "None" } } }
App Transport Security
Configure ATS
xml<!-- Info.plist --> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSExceptionDomains</key> <dict> <key>api.example.com</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <key>NSExceptionMinimumTLSVersion</key> <string>TLSv1.2</string> </dict> </dict> </dict>
Data Protection
Enable Data Protection
- 1Go to Project Settings > Capabilities
- 2Enable "Data Protection"
- 3Choose protection level:
- Complete Protection (default) - Protected Unless Open - Protected Until First User Authentication
File Protection
swiftclass SecureFileManager { func writeProtectedData(_ data: Data, to url: URL) throws { try data.write(to: url) try addFileProtection(to: url) } private func addFileProtection(to url: URL) throws { var attributes = [FileAttributeKey: Any]() attributes[.protectionKey] = FileProtectionType.complete try FileManager.default.setAttributes(attributes, ofItemAtPath: url.path) } }
Input Validation
Validate User Input
swiftclass InputValidator { func validateEmail(_ email: String) -> Bool { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let predicate = NSPredicate(format: "SELF MATCHES %@", emailRegex) return predicate.evaluate(with: email) } func validatePassword(_ password: String) -> Bool { // At least 8 characters, 1 uppercase, 1 lowercase, 1 number let passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$" let predicate = NSPredicate(format: "SELF MATCHES %@", passwordRegex) return predicate.evaluate(with: password) } func sanitizeInput(_ input: String) -> String { return input .trimmingCharacters(in: .whitespacesAndNewlines) .filter { $0.isASCII } } }
Best Practices
- 1Keychain: Use Keychain for sensitive data
- 2Encryption: Encrypt data at rest and in transit
- 3HTTPS: Always use HTTPS for network calls
- 4Biometrics: Implement biometric authentication
- 5Validation: Validate all user inputs
- 6Updates: Keep security libraries updated
- 7Testing: Test for security vulnerabilities
- 8Privacy: Minimize data collection
Security is an ongoing process. Stay vigilant and protect user data!