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
- 1Go to Project Settings > Capabilities
- 2Enable "Data Protection"
- 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
- 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!