Skip to content
All essays
iOSMarch 27, 202514 min

Swift Security Best Practices: Protect User Data

Implement security best practices in Swift apps. Encryption, keychain, and secure data storage.

Ü
Ümit Uz
Mobile & Full Stack Developer

Security is paramount in iOS app development. Learn to implement encryption, secure storage, and protect user data effectively.

Keychain Services

Store Sensitive Data

swift
import 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

swift
import 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

swift
class 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

swift
import 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

swift
class 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

swift
import 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

  1. 1Go to Project Settings > Capabilities
  2. 2Enable "Data Protection"
  3. 3Choose protection level:

- Complete Protection (default) - Protected Unless Open - Protected Until First User Authentication

File Protection

swift
class 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

swift
class 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

  1. 1Keychain: Use Keychain for sensitive data
  2. 2Encryption: Encrypt data at rest and in transit
  3. 3HTTPS: Always use HTTPS for network calls
  4. 4Biometrics: Implement biometric authentication
  5. 5Validation: Validate all user inputs
  6. 6Updates: Keep security libraries updated
  7. 7Testing: Test for security vulnerabilities
  8. 8Privacy: Minimize data collection

Security is an ongoing process. Stay vigilant and protect user data!

Next essay
Swift Background Tasks: Schedule and Execute