NSFileProtectionComplete不加密核心数据文件

时间:2016-08-25 18:01:03

标签: core-data encryption swift2 xcode7

我正在使用Xcode 7.3 for iOS 9.3来尝试加密Core Data文件。我正在尝试使用NSPersistentStoreFileProtectionKey并将其设置为NSFileProtectionComplete以启用加密。它由于某种原因无法工作,我总能看到应用程序生成的.sqlite文件,并浏览sqlitebrowser或iexplorer中的内容。这是我的代码:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.

    // Create the coordinator and store
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
    var failureReason = "There was an error creating or loading the application's saved data."


    let dict: [NSObject : AnyObject] = [
        NSPersistentStoreFileProtectionKey        : NSFileProtectionComplete
    ]

    do {
        try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: dict)
    } catch {
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason

        dict[NSUnderlyingErrorKey] = error as NSError
        let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
        abort()
    }

    do {
        let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
        try NSFileManager.defaultManager().setAttributes([NSFileProtectionKey : NSFileProtectionComplete], ofItemAtPath: url.path!)

    } catch {

    }

    do {
        let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite-wal")
        try NSFileManager.defaultManager().setAttributes([NSFileProtectionKey : NSFileProtectionComplete], ofItemAtPath: url.path!)
        //            try print(NSFileManager.defaultManager().attributesOfFileSystemForPath(String(url)))

    } catch {

    }

    do {
        let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite-shm")
        try NSFileManager.defaultManager().setAttributes([NSFileProtectionKey : NSFileProtectionComplete], ofItemAtPath: url.path!)
        //            try print(NSFileManager.defaultManager().attributesOfFileSystemForPath(String(url)))

    } catch {

    }


    return coordinator
}()

我还在“功能”中为我的目标启用了数据保护。我已从Apple Developer门户重新生成配置文件,并将其与启用数据保护一起使用。

我还使用以下代码检查.sqlite,.sqlite-wal和.sqlite-shm文件的文件属性。正确为所有3个NSFileProtectionKey设置了它。

func checkProtectionForLocalDb(atDir : String){

    let fileManager = NSFileManager.defaultManager()
    let enumerator: NSDirectoryEnumerator = fileManager.enumeratorAtPath(atDir)!


    for path in enumerator {

        let attr : NSDictionary = enumerator.fileAttributes!
        print(attr)


    }


}

我还尝试禁用日记模式以防止创建-wal和-shm文件。但我仍然可以阅读.sqlite文件。即使属性读取NSFileProtectionComplete。

正如在Apple Docs下的“使用磁盘加密保护数据”下的Apple文档中所述,我尝试检查变量protectedDataAvailable的值是否发生了变化,如下面的代码所示

public func applicationDidEnterBackground(application: UIApplication) {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    NSThread.sleepForTimeInterval(10)
    sleep(10)
    let dataAvailable : Bool = UIApplication.sharedApplication().protectedDataAvailable
    print("Protected Data Available : " + String(dataAvailable))

}

如果我在没有延迟的情况下检查该值,则将其设置为true,但在添加延迟后将其设置为false。然而,当我下载容器以显示内容时,它仍然具有.sqlite文件,在sqlitebrowser中打开时仍会显示内容。

4 个答案:

答案 0 :(得分:6)

好的,我终于理解了这一点。

使用Xcode 7.3.1 ..

启用文件保护

  1. 使用应用目标
  2. 上的功能标签启用文件保护
  3. 如果您不想使用默认的NSFileProtectionComplete,请在应用程序ID下的开发人员门户中更改此设置
  4. 确保XCode具有此创建的新配置文件。
  5. 为了保护您的应用所创建的文件,就是这样。
  6. 要保护Core Data,您需要将NSPersistentStoreFileProtectionKey:NSFileProtectionComplete选项添加到持久性存储中。
  7. 示例:

    var options: [NSObject : AnyObject] = [NSMigratePersistentStoresAutomaticallyOption: true,
                       NSPersistentStoreFileProtectionKey: NSFileProtectionComplete,
                    NSInferMappingModelAutomaticallyOption: true]
        do {
            try coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: options)
    

    测试文件保护

    我无法使用连接到计算机的非越狱设备对此进行测试。每次尝试以这种方式访问​​设备都需要我信任"计算机,我相信可信赖的计算机始终能够读取手机的数据("可信计算机可以与您的iOS设备同步,创建备份,以及访问您设备的照片,视频,联系人和其他内容" - https://support.apple.com/en-us/HT202778)。我认为SO引用此技术的其他答案对于更新版本的iOS不再有效。实际上,我总是能够使用XCode下载容器并使用iPhone Explorer查看应用程序的数据。那么如何测试...

    1 - 通过从命令行在.app文件上运行以下命令,创建存档并确保其具有正确的权利

    codesign -d --entitlements :- <path_to_app_binary>
    

    您应该看到代表您的数据保护级别的键/值对。在这个例子中,NSFileProtectionComplete:

    <key>com.apple.developer.default-data-protection</key>
    <string>NSFileProtectionComplete</string>
    

    此外,我使用以下两种技术来确保数据保护确实有效。它们都需要更改代码。

    2 - 添加一些代码以验证是否在您的文件和/或核心数据存储上设置了正确的NSFileProtectionKey

    NSFileManager.defaultManager().attributesOfItemAtPath(dbPath.path!)
    

    如果我在我的一个文件上打印出来,我会得到:

    ["NSFileCreationDate": 2016-10-14 02:06:39 +0000, "NSFileGroupOwnerAccountName": mobile, "NSFileType": NSFileTypeRegular, "NSFileSystemNumber": 16777218, "NSFileOwnerAccountName": mobile, "NSFileReferenceCount": 1, "NSFileModificationDate": 2016-10-14 02:06:39 +0000, "NSFileExtensionHidden": 0, "NSFileSize": 81920, "NSFileGroupOwnerAccountID": 501, "NSFileOwnerAccountID": 501, "NSFilePosixPermissions": 420, "NSFileProtectionKey": NSFileProtectionComplete, "NSFileSystemFileNumber": 270902]
    

    注意&#34; NSFileProtectionKey&#34;:NSFileProtectionComplete对。

    3 - 修改以下代码并将其连接到应用中的某个按钮。

    @IBAction func settingButtonTouch(sender: AnyObject) {
            updateTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self,
                                                                 selector: #selector(TabbedOverviewViewController.runTest), userInfo: nil, repeats: true)
            registerBackgroundTask()
    }
    
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    var updateTimer: NSTimer?
    
    func registerBackgroundTask() {
        backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
            [unowned self] in
            self.endBackgroundTask()
        }
        assert(backgroundTask != UIBackgroundTaskInvalid)
    }
    
    func endBackgroundTask() {
        NSLog("Background task ended.")
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
        backgroundTask = UIBackgroundTaskInvalid
    }
    
    func runTest() {
        switch UIApplication.sharedApplication().applicationState {
        case .Active:
            NSLog("App is active.")
            checkFiles()
        case .Background:
            NSLog("App is backgrounded.")
            checkFiles()
        case .Inactive:
            break
        }
    }
    
    func checkFiles() {
        //attempt to access a protected resource, i.e. a core data store or file
    }
    

    当您点击按钮时,此代码每隔0.5秒开始执行checkFiles方法。这应该在应用程序的前台或后台无限期运行 - 直到您锁定手机。此时它应该在大约10秒后可靠地失败 - 完全如NSFileProtectionComplete的描述中所述。

答案 1 :(得分:0)

我没有为本地级别的文件加密,而是为应用程序整体设置了NSFileProtectionComplete。

创建文件&#39; entitlements.plist&#39;在您的应用根目录中包含以下内容。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>DataProtectionClass</key>
    <string>NSFileProtectionComplete</string>
</dict>
</plist>

然后,如果您还没有这样做(这可能是文件级加密的问题),请在您的应用功能中启用数据保护。

enter image description here

答案 2 :(得分:0)

我们需要了解数据保护的工作原理。 实际上,您甚至不需要启用它。从iOS7开始,默认保护级别为“文件保护完成,直到首次用户认证。”

这意味着只有在用户首次解锁设备后才能访问文件。之后,即使设备已锁定,文件仍可访问,直到关闭或重启为止。

另一件事是,您将始终在受信任的计算机上查看应用程序的数据-不管数据保护级别设置如何。

但是,如果有人尝试直接从闪存驱动器中读取数据,则无法访问数据。数据保护的目的是确保不会从受密码保护的设备的存储中提取敏感数据。

运行此代码后,即使锁定设备,我仍然可以访问和读取写入protectedFileURL的内容。

    do {
        try data.write(to: protectedFileURL, options: .completeFileProtectionUnlessOpen)
    } catch {
        print(error)
    }

但这是正常的,因为我是在受信任的计算机上运行iExplorer的。 出于同样的原因,如果您看到sqlite文件也可以。

如果设备丢失或被盗,情况会有所不同。黑客将无法读取sqlite文件,因为该文件已加密。好吧,除非他以某种方式猜出了您的密码。

答案 3 :(得分:0)

Swift 5.0和Xcode 11

  1. 在“功能”中启用“数据保护”。
  2. 使用以下代码在特定路径下保护文件或文件夹:

    // Protects a file or folder + excludes it from backup.
    // - parameter path: Path component of the file.
    // - parameter fileProtectionType: `FileProtectionType`.
    // - returns: True, when protected successful.
    static func protectFileOrFolderAtPath(_ path: String, fileProtectionType: FileProtectionType) -> Bool {
        guard FileManager.default.fileExists(atPath: path) else { return false }
    
        let fileProtectionAttrs = [FileAttributeKey.protectionKey: fileProtectionType]
        do {
            try FileManager.default.setAttributes(fileProtectionAttrs, ofItemAtPath: path)
            return true
        } catch {
            assertionFailure("Failed protecting path with error: \(error).")
            return false 
        }
    }
    
  3. (可选),使用以下代码检查特定路径下的文件或文件夹是否受保护(注意:仅适用于物理设备):

    /// Returns true, when the file at the provided path is protected.
    /// - parameter path: Path of the file to check.
    /// - note: Returns true, for simulators. Simulators do not have hardware file encryption. This feature is only available for real devices.
    static func isFileProtectedAtPath(_ path: String) -> Bool {
        guard !Environment.isSimulator else { return true } // file protection does not work on simulator!
        do {
            let attributes = try FileManager.default.attributesOfItem(atPath: path)
            if attributes.contains(where: { $0.key == .protectionKey }) {
                return true
            } else {
                return false
            }
        } catch {
            assertionFailure(String(describing: error))
            return false
        }
    }