Skip to content
advanced Phase 12 · Security

Data Protection

Use iOS data protection classes, file encryption, and Keychain data protection.

50m
3 problems
Topic Progress 0%

Data Protection Classes

iOS Data Protection Overview

iOS provides four data protection classes that determine when encrypted data is accessible:

Protection Class When Accessible Use Case
.complete Only when device is unlocked Highly sensitive data
.completeUnlessOpen When unlocked or file is already open Background downloads
.completeAfterFirstUserAuthentication After first unlock Data needed at boot
.completeUntilFirstUserAuthentication Until device restarts Session data

.complete Protection

Data is encrypted and only accessible when the device is unlocked:

let fileURL = documentsDirectory.appendingPathComponent("sensitive.dat")
let options: [FileAttributeKey: Any] = [
    .protectionKey: FileProtectionType.complete
]

FileManager.default.createFile(
    atPath: fileURL.path,
    contents: sensitiveData,
    attributes: options
)

.completeUnlessOpen Protection

Data remains accessible if the file is already open when the device locks. Useful for background downloads that need to continue:

let options: [FileAttributeKey: Any] = [
    .protectionKey: FileProtectionType.completeUnlessOpen
]

FileManager.default.createFile(
    atPath: fileURL.path,
    contents: data,
    attributes: options
)

.completeAfterFirstUserAuthentication

Data becomes accessible after the user unlocks the device for the first time after a restart. This is the most common protection level for most apps:

let options: [FileAttributeKey: Any] = [
    .protectionKey: FileProtectionType.completeAfterFirstUserAuthentication
]

Choosing the Right Level

  • .complete: Banking credentials, health data, payment information
  • .completeUnlessOpen: Email attachments being downloaded, media downloads
  • .completeAfterFirstUserAuthentication: User preferences, cached data, most app data
  • .completeUntilFirstUserAuthentication: Authentication tokens, session data

File Encryption

Applying Protection to Files

When you create or modify a file, specify the protection level in the attributes:

func saveSecureFile(_ data: Data, named filename: String) throws {
    let fileURL = documentsDirectory.appendingPathComponent(filename)
    
    let attributes: [FileAttributeKey: Any] = [
        .protectionKey: FileProtectionType.completeAfterFirstUserAuthentication
    ]
    
    try data.write(to: fileURL, options: [.completeFileProtection])
}

Modifying Existing File Protection

Change the protection level of an existing file:

func changeProtectionLevel(for url: URL, to level: FileProtectionType) throws {
    try FileManager.default.setAttributes(
        [.protectionKey: level],
        ofItemAtPath: url.path
    )
}

Encryption with CryptoKit

For additional encryption beyond file protection, use CryptoKit:

import CryptoKit

class EncryptionService {
    private let key: SymmetricKey
    
    init() {
        // In production, store key in Keychain
        self.key = SymmetricKey(size: .bits256)
    }
    
    func encrypt(_ data: Data) throws -> Data {
        let sealed = try AES.GCM.seal(data, using: key)
        return sealed.combined!
    }
    
    func decrypt(_ data: Data) throws -> Data {
        let sealedBox = try AES.GCM.SealedBox(combined: data)
        return try AES.GCM.open(sealedBox, using: key)
    }
}

Secure Deletion

Overwrite data before deletion to prevent forensic recovery:

func secureDelete(at url: URL) throws {
    let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
    guard let fileSize = attributes[.size] as? Int else { return }
    
    // Overwrite with random data
    let randomData = Data((0..<fileSize).map { _ in UInt8.random(in: 0...255) })
    try randomData.write(to: url, options: .atomic)
    
    // Then delete
    try FileManager.default.removeItem(at: url)
}

Keychain Protection

Keychain Access Control

The Keychain stores sensitive data with hardware-backed encryption. Set access control to require biometrics or passcode:

import Security

class KeychainService {
    func save(_ data: Data, for key: String, requiresBiometric: Bool) throws {
        let access: SecAccessControl?
        if requiresBiometric {
            access = SecAccessControlCreateWithFlags(
                kCFAllocatorDefault,
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                [.biometryCurrentSet, .or, .devicePasscode],
                nil
            )
        } else {
            access = nil
        }
        
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw KeychainError.saveFailed(status)
        }
    }
}

Keychain Accessibility Levels

Level When Accessible
WhenUnlocked Device unlocked
AfterFirstUnlock After first unlock post-restart
WhenUnlockedThisDeviceOnly Unlocked, not transferred to new device
AfterFirstUnlockThisDeviceOnly After first unlock, not transferred

Best Practices

  • Use ThisDeviceOnly variants to prevent keychain data from transferring to new devices via backup
  • Store encryption keys, tokens, and credentials in Keychain
  • Set appropriate access control flags for biometric protection
  • Handle Keychain errors gracefully and provide fallback authentication
  • Never store large data in Keychain, use encrypted files instead

Quiz

1. When is data with .complete protection accessible?

Question 1 options

2. Which protection class should you use for email attachments being downloaded?

Question 2 options

3. Why should you use ThisDeviceOnly Keychain variants?

Question 3 options

4. What framework provides symmetric encryption in iOS?

Question 4 options

Flashcards

Question

What are the four iOS data protection classes?

Answer

.complete (unlocked only), .completeUnlessOpen (unlocked or file open), .completeAfterFirstUserAuthentication (after first unlock), .completeUntilFirstUserAuthentication (until restart).

Question

What is the difference between Keychain and NSUserDefaults for sensitive data?

Answer

Keychain encrypts data with hardware-backed security and supports access control. NSUserDefaults stores data in plaintext plist files.

Question

When should you use .complete file protection?

Answer

For highly sensitive data like banking credentials, health data, and payment information that should only be accessible when the device is unlocked.

Revision Notes

Key Takeaways

  • 1. iOS provides four data protection classes for different security needs
  • 2. .completeAfterFirstUserAuthentication is the most common protection level
  • 3. Keychain provides hardware-backed encryption for sensitive data
  • 4. CryptoKit offers modern symmetric and asymmetric encryption
  • 5. Always use ThisDeviceOnly variants to prevent data transfer

Interview Tips

  • Explain the four data protection classes and when to use each
  • Describe how Keychain differs from NSUserDefaults for sensitive data
  • Discuss how to implement file encryption with CryptoKit
  • Walk through securing user authentication tokens

Cheat Sheet

Data Protection Quick Reference

  • .complete: unlocked only
  • .completeUnlessOpen: unlocked or file already open
  • .completeAfterFirstUserAuthentication: after first unlock (most common)
  • Keychain for tokens, keys, credentials
  • CryptoKit for additional encryption
  • Use ThisDeviceOnly Keychain variants