whenPasscodeSetThisDeviceOnly模式下Keychain值二次被擦除求助
Keychain值在重设密码后锁屏10秒被擦除的问题分析与解决
我来帮你拆解这个Keychain的异常问题——这个现象其实和iOS底层的加密机制初始化流程有关,先从背景和问题细节说起:
背景代码
我们使用KeychainAccess库实现Keychain存储,代码如下:
private let keychain = Keychain(service: "som.service.name") func getProperty(_ prop: String) -> String? { return try? keychain.get(prop) } func setProperty(_ prop: String, value: String) { try? keychain.accessibility(.whenPasscodeSetThisDeviceOnly).set(value, key: prop) }
问题现象
使用.whenPasscodeSetThisDeviceOnly属性时,预期逻辑是:
- 移除设备密码后,Keychain值自动擦除(这部分符合预期)
- 重设新密码后,能正常存储、检索新值
但实际出现了奇怪的异常:重设密码并存储新值后,首次锁屏停留至少10秒再解锁,刚存的Keychain值会被再次擦除;而之后的锁屏操作就不会再影响Keychain的持久性,一切恢复正常。
复现步骤
- 设置设备密码,正常存储/检索Keychain值(无问题)
- 完全移除设备密码(注意:修改密码不会触发这个问题,必须是彻底删除密码)
- 设置新设备密码,不锁屏直接打开应用
- 尝试检索旧值→值已被擦除(符合预期)
- 存储新值并即时检索(正常工作)
- 锁定设备并等待至少10秒
- 解锁后检索新值→值被擦除(异常点)
- 首次10秒锁屏后,后续锁屏操作不再影响Keychain持久性
原因拆解
这个问题本质是iOS Keychain的加密上下文初始化延迟导致的:
- 移除密码时,iOS会自动清理所有标记为
.whenPasscodeSetThisDeviceOnly的Keychain项——这是Apple设计的安全机制,符合预期。 - 重设密码后,iOS会在后台启动设备加密环境的重新初始化流程,这个过程不是瞬间完成的,通常需要几秒到十几秒的时间。
- 在初始化完成前,你存储的新Keychain项虽然能被即时访问,但并没有真正绑定到新的密码加密上下文。当设备锁屏并停留足够时间后,iOS会触发Keychain的一致性校验,发现这些“未正确绑定”的项,就会将其删除。
- 一旦加密环境初始化完成,后续存储的Keychain项就会正确关联到新密码,锁屏操作就不会再触发删除逻辑。
解决思路与可行方案
方案1:等待加密环境就绪后再存储
利用iOS的系统通知,等设备的加密环境完全初始化完成后再执行存储操作:
import Security import UIKit // 监听加密环境就绪通知 NotificationCenter.default.addObserver(forName: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil, queue: .main) { [weak self] _ in // 当protectedData可用时,说明加密上下文已就绪 self?.safeStoreProperty("yourKey", value: "yourValue") } func safeStoreProperty(_ prop: String, value: String) { // 先确认设备已设置密码 var isPasscodeSet = false let status = SecItemCopyMatching([ kSecClass: kSecClassGenericPassword, kSecAttrService: "som.service.name", kSecAttrAccessible: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecReturnAttributes: kCFBooleanTrue! ] as CFDictionary, nil) if status != errSecItemNotFound && status != errSecAuthFailed { isPasscodeSet = true } if isPasscodeSet { try? keychain.accessibility(.whenPasscodeSetThisDeviceOnly).set(value, key: prop) } }
UIApplication.protectedDataDidBecomeAvailableNotification会在设备加密环境完全就绪、受保护数据可访问时触发,此时存储的Keychain项会正确绑定到新密码,避免后续被删除。
方案2:调整Keychain访问属性(业务允许的话)
如果你的业务场景对安全性要求可以稍微放宽,可以考虑用.afterFirstUnlockThisDeviceOnly替代.whenPasscodeSetThisDeviceOnly。这个属性的Keychain项不会在移除密码时被删除,同时也能避开加密初始化延迟的问题,但要注意:没有密码时,设备被物理获取的话,这些项存在被访问的风险。
方案3:添加检索重试机制
结合业务逻辑,在检索Keychain值失败时自动重新存储(需要注意避免无限重试):
func getPropertyWithRetry(_ prop: String, fallbackValue: String? = nil) -> String? { // 先尝试正常检索 if let value = try? keychain.get(prop) { return value } // 检索失败时,尝试存储 fallback 值(根据你的业务需求调整) guard let fallback = fallbackValue else { return nil } try? keychain.accessibility(.whenPasscodeSetThisDeviceOnly).set(fallback, key: prop) return fallback }
内容的提问来源于stack exchange,提问作者Przemysław Jaskot
相关产品推荐
相关产品推荐

