关于 axiom-file-protection-ref
面向 iOS 开发者的文件保护 API 参考技能,介绍不同保护级别、文件可访问时机、后台行为,以及与 Keychain 的取舍。
主要功能
- 01比较四种文件保护级别
- 02设置新文件和现有文件的保护
- 03检查文件当前保护级别
- 04评估后台访问行为
- 05比较文件保护与 Keychain
- 06排查受保护文件的访问问题
适用场景
- 01为敏感文档选择保护级别
- 02解决设备锁定时的文件访问失败
- 03支持后台任务读取文件
- 04为机密信息与大文件选择存储方式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-file-protection-ref安装后在 Claude Code 中输入/axiom-file-protection-ref确认已加载。
详细介绍
iOS 文件保护参考
用途:文件加密和数据保护 API 的综合参考 可用性:iOS 4.0+(所有保护级别),iOS 26 中的最新增强功能 背景:基于 iOS 数据保护架构,使用硬件加密
何时使用此技能
在以下情况下使用此技能:
- 保护静态存储的敏感用户数据
- 为文件选择合适的 FileProtectionType
- 了解文件何时可访问/加密
- 调试设备锁定后出现的“无法访问文件”错误
- 实现安全文件存储
- 比较 Keychain 与文件保护方案
- 处理后台文件访问需求
概述
iOS 数据保护提供与设备密码关联的硬件加速文件加密。用户设置密码后,每个文件都可以使用受该密码保护的密钥进行加密。
核心概念:
- 启用保护后,文件会自动加密
- 加密密钥由设备硬件和用户密码派生
- 设备锁定时,文件会变得无法访问(具体取决于保护级别)
- 不会产生性能开销(硬件 AES 加密)
保护级别对比
| 级别 | 加密持续到 | 可访问时机 | 用途 | 后台访问 |
|---|---|---|---|---|
| complete | 设备解锁 | 仅在解锁期间 | 敏感数据(健康、财务) | ❌ 否 |
| completeUnlessOpen | 文件关闭 | 首次解锁后,文件打开期间 | 大型下载、视频 | ✅ 文件已打开时可访问 |
| completeUntilFirstUserAuthentication | 开机后首次解锁 | 首次解锁后 | 大多数应用数据 | ✅ 是 |
| none | 从不 | 始终 | 公开缓存、临时文件 | ✅ 是 |
保护级别详细说明
.complete
完整说明:
“文件以加密格式存储在磁盘上;设备锁定或启动时,无法读取或写入该文件。”
适用场景:
- 用户健康数据
- 财务信息
- 密码库
- 敏感文档
- 个人照片(如果应用需要最高安全性)
行为:
- 已加密:✅ 始终
- 可访问:仅在设备解锁时
- 后台访问:❌ 否(设备锁定时应用无法读取)
- 开机后可用:❌ 否(直到用户解锁)
代码示例:
// ✅ 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
)
权衡:
- ✅ 最高安全性
- ❌ 无法在后台访问
- ❌ 应用尝试在设备锁定时访问时,用户会看到错误
.completeUnlessOpen
完整说明:
“文件关闭后,会以加密格式存储在磁盘上。”
适用场景:
- 大型文件下载(可在后台继续)
- 正在播放的视频文件
- 正在编辑的文档
- 任何需要在打开期间支持后台访问的文件
行为:
- 已加密:✅ 关闭时
- 可访问:首次解锁后可访问,文件打开期间仍可访问
- 后台访问:✅ 是(文件已打开时)
- 开机后可用:❌ 否(直到首次解锁)
代码示例:
// ✅ 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
}
权衡:
- ✅ 安全性良好(不使用时会加密)
- ✅ 支持后台访问(文件已打开时)
- ⚠️ 文件打开期间存在风险
.completeUntilFirstUserAuthentication
完整说明:
“文件以加密格式存储在磁盘上;设备启动后才能访问。”
适用场景:
- 大多数应用数据
- 用户偏好设置
- 已下载内容
- 数据库文件
- 任何需要后台访问的内容
行为:
- 已加密:✅ 始终
- 可访问:开机后首次解锁后
- 后台访问:✅ 是(首次解锁后)
- 开机后可用:❌ 否(直到用户至少解锁一次)
对于大多数文件,这是推荐的默认设置。
代码示例:
// ✅ 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)
}
权衡:
- ✅ 启动期间受保护(设备关机时被盗)
- ✅ 支持后台访问(正常运行期间)
- ⚠️ 设备锁定时仍可访问(保护性低于 .complete)
.none
完整说明:
“此文件没有任何特殊保护。”
适用场景:
- 公共缓存数据
- 临时文件
- 非敏感下载内容
- 仅在绝对必要时使用
行为:
- 加密:❌ 从不
- 可访问:✅ 始终
- 后台访问:✅ 始终
- 启动后可用:✅ 始终
代码示例:
// ⚠️ USE SPARINGLY: Only for truly non-sensitive data
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
)
对现有文件
// ✅ CORRECT: Change protection on existing file
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
)
检查当前保护
// ✅ 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")
}
}
文件保护与 Keychain
决策矩阵
| 用例 | 推荐方案 | 原因 |
|---|---|---|
| 密码、令牌、密钥 | Keychain | 专为小型机密信息设计 |
| 小型敏感值(<几 KB) | Keychain | 更安全,单独加密 |
| 文件 >1 KB | 文件保护 | 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 的优势:
- 更精细的访问控制(Face ID/Touch ID)
- 单独加密(不依赖文件系统)
- 应用删除后仍然保留(如果已配置)
文件保护的优势:
- 可与现有文件操作配合使用
- 高效处理大型数据
- 只需少量代码即可自动生效
后台访问注意事项
iOS 后台模式与文件保护
// ❌ 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
)
处理保护错误
// ✅ 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
}
}
iCloud 与文件保护
文件保护如何与 iCloud 配合使用
本地文件保护:
- 应用于本地缓存副本
- 不会影响存储在 iCloud 中的版本
- iCloud 有自己的加密机制(传输中和静态数据)
iCloud 加密:
- 所有 iCloud 数据在静态存储时都会加密(由 Apple 管理密钥)
- 某些数据类型支持端到端加密(高级数据保护)
- 文件保护只影响本地设备
// ✅ 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
}
常见模式
模式 1:新应用的默认保护
// ✅ 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
}
模式 2:加密数据库文件
// ✅ 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)
)
模式 3:为后台任务降低保护级别
// ⚠️ 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
调试文件保护问题
问题:后台无法访问文件
症状:后台任务读取文件失败
// 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
问题:重启后无法访问文件
症状:设备重启后,应用无法立即访问文件
原因:使用了 .complete 或 .completeUntilFirstUserAuthentication(符合预期)
解决方案:这是预期行为。可以:
- 等待用户解锁设备
- 通过适当的 UI 优雅处理
- 对必须可访问的文件使用
.none(安全性权衡)
权限配置
文件保护通常无需特殊权限配置,但某些功能需要:
数据保护权限
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
需要时机:
- 使用
.complete保护 - 某些 iOS 版本要求使用任何保护级别时都配置(请查阅文档)
添加方法:
- Xcode → Target → Signing & Capabilities
- “+ Capability” → Data Protection
- 选择保护级别
快速参考表
| 场景 | 推荐的保护级别 | 锁定时是否可访问? | 是否可在后台访问? |
|---|---|---|---|
| 用户健康数据 | .complete | ❌ 否 | ❌ 否 |
| 财务记录 | .complete | ❌ 否 | ❌ 否 |
| 大多数应用数据 | .completeUntilFirstUserAuthentication | ✅ 是(首次解锁后) | ✅ 是 |
| 下载内容(大文件) | .completeUnlessOpen | ✅ 打开时可访问 | ✅ 打开时可访问 |
| 数据库文件 | .completeUntilFirstUserAuthentication | ✅ 是 | ✅ 是 |
| 已下载的图片 | .completeUntilFirstUserAuthentication | ✅ 是 | ✅ 是 |
| 公开缓存 | .none | ✅ 是 | ✅ 是 |
| 临时文件 | .none | ✅ 是 | ✅ 是 |
相关技能
axiom-storage— 确定何时使用文件保护,而非其他安全措施axiom-storage-management-ref— 文件生命周期、清理和磁盘管理axiom-storage-diag— 调试文件访问问题axiom-keychain— 安全存储凭据(令牌、密码、密钥)axiom-keychain-ref— 完整的 SecItem API 参考axiom-cryptokit— 使用 CryptoKit 加密和签名
最后更新:2025-12-12 技能类型:参考 最低 iOS 版本:4.0(所有保护级别) 最新更新:iOS 26