You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 11:28:30