iOS应用更新时EncryptedCoreData加密现有Core Data触发无效密码错误
未加密Core Data迁移到EncryptedCoreData v2的无缝解决方案
错误根源
覆盖安装时,原应用的未加密SQLite数据库文件未被移除,EncryptedStore尝试用指定密钥解密该未加密文件,导致底层SQLite返回file is not a database错误,上层被封装为Incorrect passcode的错误提示。
实现方案
核心逻辑:先检测未加密数据库是否存在,存在则先通过标准Core Data加载数据,迁移至加密数据库后替换原存储文件;不存在则直接初始化EncryptedStore。
1. 定义路径常量
// 原未加密数据库路径 private let originalSQLiteURL = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("YourModel.sqlite") // 临时加密数据库路径(迁移完成后替换原路径) private let tempEncryptedSQLiteURL = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("YourModel_encrypted.sqlite")
2. 检测未加密存储是否存在
let hasUnencryptedStore = FileManager.default.fileExists(atPath: originalSQLiteURL.path)
3. 分场景初始化Core Data
场景1:存在未加密数据库,执行迁移
if hasUnencryptedStore { // 加载未加密Core Data容器 let originalContainer = NSPersistentContainer(name: "YourModel") originalContainer.loadPersistentStores { _, loadError in guard loadError == nil else { fatalError("加载未加密存储失败: \(loadError!)") } // 准备加密容器配置 let encryptionKey = "YourSecureEncryptionKey" // 替换为实际密钥,建议存入Keychain let encryptedOptions: [AnyHashable: Any] = [ EncryptedStorePassphraseKey: encryptionKey, EncryptedStoreDatabaseLocation: tempEncryptedSQLiteURL, NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] do { let encryptedStoreDesc = try EncryptedStore.makeDescription(options: encryptedOptions, configuration: nil) let encryptedContainer = NSPersistentContainer(name: "YourModel") encryptedContainer.persistentStoreDescriptions = [encryptedStoreDesc] encryptedContainer.loadPersistentStores { _, encryptLoadError in guard encryptLoadError == nil else { fatalError("加载加密存储失败: \(encryptLoadError!)") } // 迁移数据:从原上下文复制到加密上下文 originalContainer.viewContext.performAndWait { encryptedContainer.viewContext.performAndWait { let entityNames = originalContainer.managedObjectModel.entities.compactMap { $0.name } for entityName in entityNames { let fetchReq = NSFetchRequest<NSManagedObject>(entityName: entityName) do { let objects = try originalContainer.viewContext.fetch(fetchReq) for obj in objects { // 创建新对象并复制属性 let newObj = NSEntityDescription.insertNewObject(forEntityName: entityName, into: encryptedContainer.viewContext) for attr in obj.entity.attributesByName.keys { newObj.setValue(obj.value(forKey: attr), forKey: attr) } // 处理关联关系(根据模型复杂度调整) for rel in obj.entity.relationshipsByName.keys { if let relatedSet = obj.value(forKey: rel) as? Set<NSManagedObject> { let newRelatedSet = relatedSet.map { relatedObj in let newRelated = NSEntityDescription.insertNewObject(forEntityName: relatedObj.entity.name!, into: encryptedContainer.viewContext) for attr in relatedObj.entity.attributesByName.keys { newRelated.setValue(relatedObj.value(forKey: attr), forKey: attr) } return newRelated } newObj.setValue(Set(newRelatedSet), forKey: rel) } else if let relatedObj = obj.value(forKey: rel) as? NSManagedObject { let newRelated = NSEntityDescription.insertNewObject(forEntityName: relatedObj.entity.name!, into: encryptedContainer.viewContext) for attr in relatedObj.entity.attributesByName.keys { newRelated.setValue(relatedObj.value(forKey: attr), forKey: attr) } newObj.setValue(newRelated, forKey: rel) } } } } catch { print("获取\(entityName)数据失败: \(error)") } } // 保存加密上下文并替换原存储 do { try encryptedContainer.viewContext.save() // 替换原未加密文件 try FileManager.default.removeItem(at: originalSQLiteURL) try FileManager.default.copyItem(at: tempEncryptedSQLiteURL, to: originalSQLiteURL) // 清理临时文件 try FileManager.default.removeItem(at: tempEncryptedSQLiteURL) // 应用后续使用加密容器 self.persistentContainer = encryptedContainer } catch { print("保存加密数据或替换存储失败: \(error)") } } } } } catch { print("创建加密存储描述失败: \(error)") } } } else { // 场景2:无未加密存储,直接初始化EncryptedStore let encryptionKey = "YourSecureEncryptionKey" do { let options: [AnyHashable: Any] = [ EncryptedStorePassphraseKey: encryptionKey, EncryptedStoreDatabaseLocation: originalSQLiteURL, NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] let encryptedStoreDesc = try EncryptedStore.makeDescription(options: options, configuration: nil) let container = NSPersistentContainer(name: "YourModel") container.persistentStoreDescriptions = [encryptedStoreDesc] container.loadPersistentStores { _, error in guard error == nil else { fatalError("加载加密存储失败: \(error!)") } self.persistentContainer = container } } catch { let errorDict: [String: Any] = [ NSLocalizedDescriptionKey: "初始化应用存储失败", NSLocalizedFailureReasonErrorKey: "创建或加载应用保存数据时出错", NSUnderlyingErrorKey: error as NSError ] Log.d(errorDict) } }
关键注意事项
- 密钥安全:禁止硬编码密钥,使用
Keychain Services存储和获取加密密钥。 - 关系处理:上述关系迁移为基础实现,若模型包含复杂关联(如反向关系、级联规则),需补充对应逻辑保证数据完整性。
- 测试验证:必须在覆盖安装场景下测试,验证不同版本未加密数据的迁移效果,确保无数据丢失。
- 错误处理:生产环境需完善错误捕获逻辑,避免崩溃,向用户提供友好提示。
内容的提问来源于stack exchange,提问作者tech savvy
相关产品推荐
相关产品推荐

