苹果官方提供了最安全、最推荐的方式来解决这个问题,即使用 iOS Keychain 和 本地认证框架。

(图片来源网络,侵删)
下面我将详细解释其原理、实现步骤,并介绍其他替代方案及其优缺点。
核心概念:iOS Keychain 和 LocalAuthentication
-
iOS Keychain (钥匙串):
- 是什么:Keychain是iOS设备上一个专门用于存储敏感数据(如密码、密钥、令牌)的、受操作系统保护的加密数据库,它比普通的
UserDefaults或文件存储安全得多,因为它有专门的硬件级加密保护。 - 作用:我们将用户的解锁密码或一个随机生成的“访问密钥”安全地存储在Keychain中,这样,即使App被卸载重装,只要用户使用同一个Apple ID登录,这个密钥依然存在,保证了设置的持续性。
- 是什么:Keychain是iOS设备上一个专门用于存储敏感数据(如密码、密钥、令牌)的、受操作系统保护的加密数据库,它比普通的
-
LocalAuthentication (本地认证) 框架:
- 是什么:这是苹果官方提供的框架,用于在本地设备上验证用户身份。
- 作用:我们可以使用它来弹出一个系统级的验证界面,让用户使用面容ID/触控ID或设备密码进行验证,这个过程非常流畅,用户体验好,且安全性高,它不需要我们在Keychain中存储用户的生物识别数据,只是调用系统API进行验证。
推荐实现方案:Keychain + LocalAuthentication
这个方案的流程是:首次设置密码 -> 存储密钥到Keychain -> 每次启动App时弹出生物识别/密码验证 -> 验证通过后解密或直接显示内容。

(图片来源网络,侵删)
详细步骤:
第1步:创建一个密钥并存储到Keychain
这个密钥是用户设置的密码经过某种处理(用App特定的密钥进行加密)后生成的,用于后续验证,我们可以使用苹果的GenericPassword API来存储。
代码示例 (Swift):
import Security
// 存储用户设置的密码到Keychain
func storePasswordToKeychain(password: String) {
// 1. 将密码转换为Data
guard let passwordData = password.data(using: .utf8) else { return }
// 2. 设置Keychain查询条件
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "myAppAppLockPassword", // 一个唯一的标识符
kSecValueData as String: passwordData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly // 安全级别:仅在解锁设备时可用
]
// 3. 先尝试删除旧的,避免重复
SecItemDelete(query as CFDictionary)
// 4. 添加新的密码
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
print("存储密码到Keychain失败,错误码: \(status)")
} else {
print("密码已成功存储到Keychain")
}
}
// 从Keychain中读取密码
func getPasswordFromKeychain() -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "myAppAppLockPassword",
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == errSecSuccess {
if let data = dataTypeRef as? Data {
return String(data: data, encoding: .utf8)
}
}
return nil // 密码不存在
}
第2步:在App启动时进行验证
在App启动的第一个界面(通常是AppDelegate的applicationDidFinishLaunchingWithOptions或SceneDelegate的scene(_:willConnectTo:options:)中,或者ContentView的onAppear中)调用验证逻辑。
代码示例 (Swift):

(图片来源网络,侵删)
import LocalAuthentication
func showAppLockScreen() {
// 1. 首先检查Keychain中是否有存储的密码
guard let storedPassword = getPasswordFromKeychain(), !storedPassword.isEmpty else {
// 如果没有密码,说明是首次使用,直接进入App
// 或者可以跳转到设置密码的界面
print("未设置密码,直接进入App")
return
}
// 2. 使用LocalAuthentication框架进行验证
let context = LAContext()
var error: NSError?
// 检查设备是否支持生物识别
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
// 支持生物识别,弹出面容ID/触控ID界面
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "请验证身份以打开App") { [weak self] success, authenticationError in
DispatchQueue.main.async {
if success {
// 验证成功,进入App主界面
print("生物识别验证成功")
self?.proceedToApp()
} else {
// 验证失败,可以提示用户或要求输入设备密码
print("生物识别验证失败: \(authenticationError?.localizedDescription ?? "未知错误")")
self?.showPasswordInputAlert()
}
}
}
} else {
// 不支持生物识别,弹输入设备密码的界面
// 或者直接调用showPasswordInputAlert()
print("设备不支持生物识别,请使用设备密码")
showPasswordInputAlert()
}
}
// 辅助函数:显示输入密码的弹窗
func showPasswordInputAlert() {
let alertController = UIAlertController(title: "App已锁定", message: "请输入密码", preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "密码"
textField.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "确定", style: .default) { _ in
guard let password = alertController.textFields?.first?.text else { return }
// 从Keychain获取存储的密码进行比较
if password == getPasswordFromKeychain() {
print("密码验证成功")
proceedToApp()
} else {
print("密码错误")
// 可以在这里提示用户密码错误,并可以提供“忘记密码”功能
}
}
let cancelAction = UIAlertAction(title: "取消", style: .cancel)
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// 假设这是在ViewController中调用
// present(alertController, animated: true)
}
// 辅助函数:进入App主界面
func proceedToApp() {
// 执行跳转到主界面的代码,
// let rootView = AppMainView()
// let window = UIWindow(windowScene: windowScene)
// window.rootViewController = UIHostingController(rootView: rootView)
// window.makeKeyAndVisible()
}
第3步:处理忘记密码功能
这是一个非常重要的功能,如果用户忘记了密码,需要提供一个重置途径,最简单的方式是:清除Keychain中的数据,并让用户重新设置。
func forgetPassword() {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "myAppAppLockPassword"
]
let status = SecItemDelete(query as CFDictionary)
if status == errSecSuccess {
print("密码已成功清除")
// 跳转到设置密码的界面
} else {
print("清除密码失败")
}
}
替代方案及其优缺点
简单的密码输入(不使用Keychain)
- 做法:将用户设置的密码直接存储在
UserDefaults中。 - 优点:实现极其简单。
- 缺点:
- 极不安全:
UserDefaults是明文存储的,任何能访问手机文件的人(包括通过越狱工具)都能轻易看到密码。 - 数据易丢失:如果用户删除App,所有设置都会丢失。
- 极不安全:
- 强烈不推荐,仅用于非常不敏感的场景。
使用第三方加密库
- 做法:使用如
RNCryptor等第三方库,自己实现加密解密逻辑。 - 优点:灵活性高,可以实现更复杂的加密策略。
- 缺点:
- 增加项目复杂度:需要自己管理密钥和加密逻辑。
- 可能不如原生方案安全:如果密钥管理不当,同样存在安全风险。
- 用户体验差:通常只能实现输入密码验证,无法利用系统的生物识别功能。
- 在官方方案无法满足特定需求时才考虑使用。
总结与最佳实践
| 方案 | 安全性 | 用户体验 | 实现复杂度 | 推荐度 |
|---|---|---|---|---|
| Keychain + LocalAuthentication | 极高 | 极佳 (支持生物识别) | 中等 | ⭐⭐⭐⭐⭐ (强烈推荐) |
| 简单密码输入 | 极低 | 一般 | 非常简单 | ⭐ (不推荐) |
| 第三方加密库 | 高 | 一般 | 较高 | ⭐⭐ (特定场景使用) |
最佳实践建议:
- 首选官方方案:始终优先使用 Keychain + LocalAuthentication,这是苹果设计的最安全、最符合iOS人机交互规范的方案。
- 优雅降级:代码中要处理设备不支持生物识别的情况,自动回退到输入设备密码。
- 提供忘记密码入口:必须提供“忘记密码”功能,并明确告知用户后果(清除所有本地数据)。
- 明确提示:在用户首次设置密码时,明确告知他们这是为了保护App数据,并解释安全措施。
- 考虑性能:Keychain操作是同步的,虽然很快,但最好在App启动的早期阶段进行,避免阻塞主线程导致界面卡顿,对于复杂的验证逻辑,可以放在后台队列处理。
通过以上方案,你就可以为你的iOS App实现一个安全、可靠且用户体验良好的“打开加密”功能。
