Loading...
Loading...
Compare original and translation side by side
| Level | Encrypted Until | Accessible When | Use For | Background Access |
|---|---|---|---|---|
| complete | Device unlocked | Only while unlocked | Sensitive data (health, finances) | ❌ No |
| completeUnlessOpen | File closed | After first unlock, while open | Large downloads, videos | ✅ If already open |
| completeUntilFirstUserAuthentication | First unlock after boot | After first unlock | Most app data | ✅ Yes |
| none | Never | Always | Public caches, temp files | ✅ Yes |
| 保护级别 | 加密生效时段 | 可访问时段 | 适用场景 | 后台访问权限 |
|---|---|---|---|---|
| complete | 设备解锁前 | 仅设备解锁时 | 敏感数据(健康、财务信息) | ❌ 不支持 |
| completeUnlessOpen | 文件关闭后 | 首次解锁后,文件打开期间 | 大文件下载、视频文件 | ✅ 文件已打开时支持 |
| completeUntilFirstUserAuthentication | 开机后首次解锁前 | 首次解锁后 | 大多数应用数据 | ✅ 支持 |
| none | 从不加密 | 始终可访问 | 公共缓存、临时文件 | ✅ 支持 |
"The file is stored in an encrypted format on disk and cannot be read from or written to while the device is locked or booting."
// ✅ CORRECT: Maximum security for sensitive data
func saveSensitiveData(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .completeFileProtection)
}
// Or set on existing file
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: url.path
)"文件以加密格式存储在磁盘上,设备锁定或开机过程中无法读取或写入。"
// ✅ 正确用法:为敏感数据提供最高安全性
func saveSensitiveData(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .completeFileProtection)
}
// 或为已存在的文件设置保护
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: url.path
)"The file is stored in an encrypted format on disk after it is closed."
// ✅ CORRECT: Download in background, but encrypted when closed
func startBackgroundDownload(url: URL, destination: URL) throws {
try Data().write(to: destination, options: .completeFileProtectionUnlessOpen)
// Open file handle for writing
let fileHandle = try FileHandle(forWritingTo: destination)
// Download continues in background
// File remains accessible because it's open
// When closed, file becomes encrypted
// Later, when download complete:
try fileHandle.close() // Now encrypted until next unlock
}"文件关闭后以加密格式存储在磁盘上。"
// ✅ 正确用法:后台下载,关闭后加密
func startBackgroundDownload(url: URL, destination: URL) throws {
try Data().write(to: destination, options: .completeFileProtectionUnlessOpen)
// 打开文件句柄用于写入
let fileHandle = try FileHandle(forWritingTo: destination)
// 下载在后台继续
// 文件保持可访问状态,因为已打开
// 关闭后,文件将被加密
// 后续下载完成时:
try fileHandle.close() // 现在文件会被加密,直到下次解锁
}"The file is stored in an encrypted format on disk and cannot be accessed until after the device has booted."
// ✅ CORRECT: Balanced security for most app data
func saveAppData(_ data: Data, to url: URL) throws {
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
}
// ✅ This file can be accessed in background after first unlock
func backgroundTaskCanAccessFile() {
// This works even if device is locked (after first unlock)
let data = try? Data(contentsOf: url)
}"文件以加密格式存储在磁盘上,设备开机后需完成首次用户验证才可访问。"
// ✅ 正确用法:为大多数应用数据提供平衡的安全性
func saveAppData(_ data: Data, to url: URL) throws {
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
}
// ✅ 首次解锁后,即使设备锁定也可在后台访问文件
func backgroundTaskCanAccessFile() {
// 即使设备锁定(首次解锁后),此操作也能正常执行
let data = try? Data(contentsOf: url)
}"The file has no special protections associated with it."
// ⚠️ USE SPARINGLY: Only for truly non-sensitive data
func cachePublicThumbnail(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .noFileProtection)
}"文件无任何特殊保护。"
// ⚠️ 谨慎使用:仅适用于完全非敏感数据
func cachePublicThumbnail(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .noFileProtection)
}// ✅ RECOMMENDED: Set protection when writing
let sensitiveData = userData.jsonData()
try sensitiveData.write(
to: fileURL,
options: .completeFileProtection
)// ✅ 推荐用法:写入时设置保护级别
let sensitiveData = userData.jsonData()
try sensitiveData.write(
to: fileURL,
options: .completeFileProtection
)// ✅ CORRECT: Change protection on existing file
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: fileURL.path
)// ✅ 正确用法:修改已存在文件的保护级别
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.complete],
ofItemAtPath: fileURL.path
)// ✅ CORRECT: Set default protection for directory
// New files inherit this protection
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: directoryURL.path
)// ✅ 正确用法:为目录设置默认保护级别
// 目录下的新文件将继承此保护级别
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: directoryURL.path
)// ✅ Check file's current protection level
func checkFileProtection(at url: URL) throws -> FileProtectionType? {
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
return attributes[.protectionKey] as? FileProtectionType
}
// Usage
if let protection = try? checkFileProtection(at: fileURL) {
switch protection {
case .complete:
print("Maximum protection")
case .completeUntilFirstUserAuthentication:
print("Standard protection")
default:
print("Other protection")
}
}// ✅ 检查文件当前的保护级别
func checkFileProtection(at url: URL) throws -> FileProtectionType? {
let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
return attributes[.protectionKey] as? FileProtectionType
}
// 使用示例
if let protection = try? checkFileProtection(at: fileURL) {
switch protection {
case .complete:
print("Maximum protection")
case .completeUntilFirstUserAuthentication:
print("Standard protection")
default:
print("Other protection")
}
}| Use Case | Recommended | Why |
|---|---|---|
| Passwords, tokens, keys | Keychain | Designed for small secrets |
| Small sensitive values (<few KB) | Keychain | More secure, encrypted separately |
| Files >1 KB | File Protection | Keychain not designed for large data |
| User documents | File Protection | Natural file-based storage |
| Structured secrets | Keychain | Query by key, access control |
| 应用场景 | 推荐方案 | 原因 |
|---|---|---|
| 密码、令牌、密钥 | Keychain | 专为小型机密数据设计 |
| 小型敏感数据(<几KB) | Keychain | 安全性更高,单独加密 |
| 大于1KB的文件 | 文件保护 | Keychain并非为大型数据设计 |
| 用户文档 | 文件保护 | 符合基于文件的自然存储方式 |
| 结构化机密数据 | Keychain | 支持按密钥查询、访问控制 |
// ✅ CORRECT: Small secrets in Keychain
let passwordData = password.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "userPassword",
kSecValueData as String: passwordData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
// ✅ CORRECT: Files with file protection
let userData = try JSONEncoder().encode(user)
try userData.write(to: fileURL, options: .completeFileProtection)// ✅ 正确用法:小型机密数据存储在Keychain中
let passwordData = password.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "userPassword",
kSecValueData as String: passwordData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
// ✅ 正确用法:文件使用文件保护机制
let userData = try JSONEncoder().encode(user)
try userData.write(to: fileURL, options: .completeFileProtection)// ❌ WRONG: .complete files can't be accessed in background
class BackgroundTask {
func performBackgroundSync() {
// This FAILS if file has .complete protection and device is locked
let data = try? Data(contentsOf: sensitiveFileURL)
// data will be nil if device locked
}
}
// ✅ CORRECT: Use .completeUntilFirstUserAuthentication
// Files accessible in background after first unlock
try data.write(
to: fileURL,
options: .completeFileProtectionUntilFirstUserAuthentication
)// ❌ 错误用法:.complete级别的文件无法在后台访问
class BackgroundTask {
func performBackgroundSync() {
// 如果文件使用.complete保护级别且设备锁定,此操作会失败
let data = try? Data(contentsOf: sensitiveFileURL)
// 设备锁定时data为nil
}
}
// ✅ 正确用法:使用.completeUntilFirstUserAuthentication
// 首次解锁后,即使设备锁定也可在后台访问文件
try data.write(
to: fileURL,
options: .completeFileProtectionUntilFirstUserAuthentication
)// ✅ CORRECT: Handle protection errors gracefully
func readFile(at url: URL) -> Data? {
do {
return try Data(contentsOf: url)
} catch let error as NSError {
if error.domain == NSCocoaErrorDomain &&
error.code == NSFileReadNoPermissionError {
// File is protected and device is locked
print("File protected, device locked")
return nil
}
throw error
}
}// ✅ 正确用法:优雅处理保护相关错误
func readFile(at url: URL) -> Data? {
do {
return try Data(contentsOf: url)
} catch let error as NSError {
if error.domain == NSCocoaErrorDomain &&
error.code == NSFileReadNoPermissionError {
// 文件受保护且设备已锁定
print("File protected, device locked")
return nil
}
throw error
}
}// ✅ CORRECT: Protection on iCloud file affects local copy only
func saveToICloud(data: Data, filename: String) throws {
guard let iCloudURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) else { return }
let fileURL = iCloudURL.appendingPathComponent(filename)
// This protection applies to local cached copy
try data.write(to: fileURL, options: .completeFileProtection)
// iCloud has separate encryption for cloud storage
}// ✅ 正确用法:iCloud文件的保护仅影响本地副本
func saveToICloud(data: Data, filename: String) throws {
guard let iCloudURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) else { return }
let fileURL = iCloudURL.appendingPathComponent(filename)
// 此保护级别仅应用于本地缓存副本
try data.write(to: fileURL, options: .completeFileProtection)
// iCloud云存储有独立的加密机制
}// ✅ RECOMMENDED: Set default protection at app launch
func configureDefaultFileProtection() {
let fileManager = FileManager.default
let directories: [FileManager.SearchPathDirectory] = [
.documentDirectory,
.applicationSupportDirectory
]
for directory in directories {
guard let url = fileManager.urls(
for: directory,
in: .userDomainMask
).first else { continue }
try? fileManager.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: url.path
)
}
}
// Call during app initialization
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) {
configureDefaultFileProtection()
return true
}// ✅ 推荐用法:应用启动时设置默认保护级别
func configureDefaultFileProtection() {
let fileManager = FileManager.default
let directories: [FileManager.SearchPathDirectory] = [
.documentDirectory,
.applicationSupportDirectory
]
for directory in directories {
guard let url = fileManager.urls(
for: directory,
in: .userDomainMask
).first else { continue }
try? fileManager.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: url.path
)
}
}
// 应用初始化时调用
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) {
configureDefaultFileProtection()
return true
}// ✅ CORRECT: Protect SwiftData/SQLite database
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let databaseURL = appSupportURL.appendingPathComponent("app.sqlite")
// Set protection before creating database
try? FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: appSupportURL.path
)
// Now create database - it inherits protection
let container = try ModelContainer(
for: MyModel.self,
configurations: ModelConfiguration(url: databaseURL)
)// ✅ 正确用法:保护SwiftData/SQLite数据库
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let databaseURL = appSupportURL.appendingPathComponent("app.sqlite")
// 创建数据库前设置保护级别
try? FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: appSupportURL.path
)
// 现在创建数据库 - 它将继承保护级别
let container = try ModelContainer(
for: MyModel.self,
configurations: ModelConfiguration(url: databaseURL)
)// ⚠️ SOMETIMES NECESSARY: Lower protection for background access
func enableBackgroundAccess(for url: URL) throws {
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: url.path
)
}
// Only do this if:
// 1. Background access is truly required
// 2. Data sensitivity allows it
// 3. You've considered security tradeoffs// ⚠️ 仅在必要时使用:为后台访问降低保护级别
func enableBackgroundAccess(for url: URL) throws {
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: url.path
)
}
// 仅在以下情况时使用:
// 1. 确实需要后台访问权限
// 2. 数据敏感度允许降低保护级别
// 3. 已充分考虑安全性权衡// Debug: Check current protection
if let protection = try? FileManager.default.attributesOfItem(
atPath: url.path
)[.protectionKey] as? FileProtectionType {
print("Protection: \(protection)")
if protection == .complete {
print("❌ Can't access in background when locked")
}
}.completeUntilFirstUserAuthentication// 调试:检查当前保护级别
if let protection = try? FileManager.default.attributesOfItem(
atPath: url.path
)[.protectionKey] as? FileProtectionType {
print("Protection: \\(protection)")
if protection == .complete {
print("❌ 设备锁定时无法在后台访问")
}
}.completeUntilFirstUserAuthentication.complete.completeUntilFirstUserAuthentication.none.complete.completeUntilFirstUserAuthentication.none<!-- Required for: .complete protection level -->
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>.complete<!-- 适用场景:使用.complete保护级别时需要 -->
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>.complete| Scenario | Recommended Protection | Accessible When Locked? | Background Access? |
|---|---|---|---|
| User health data | | ❌ No | ❌ No |
| Financial records | | ❌ No | ❌ No |
| Most app data | | ✅ Yes (after first unlock) | ✅ Yes |
| Downloads (large files) | | ✅ While open | ✅ While open |
| Database files | | ✅ Yes | ✅ Yes |
| Downloaded images | | ✅ Yes | ✅ Yes |
| Public caches | | ✅ Yes | ✅ Yes |
| Temp files | | ✅ Yes | ✅ Yes |
| 场景 | 推荐保护级别 | 设备锁定时可访问? | 后台访问? |
|---|---|---|---|
| 用户健康数据 | | ❌ 否 | ❌ 否 |
| 财务记录 | | ❌ 否 | ❌ 否 |
| 大多数应用数据 | | ✅ 是(首次解锁后) | ✅ 是 |
| 大文件下载 | | ✅ 文件打开时是 | ✅ 文件打开时是 |
| 数据库文件 | | ✅ 是 | ✅ 是 |
| 下载的图片 | | ✅ 是 | ✅ 是 |
| 公共缓存 | | ✅ 是 | ✅ 是 |
| 临时文件 | | ✅ 是 | ✅ 是 |
axiom-storageaxiom-storage-management-refaxiom-storage-diagaxiom-storageaxiom-storage-management-refaxiom-storage-diag