NSFileProtectionComplete не шифрует файл основных данных

Я использую Xcode 7.3 для 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. NSFileProtectionKey правильно установлен для всех 3 из них.

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 на Apple Docs в разделе "Защита данных с использованием шифрования на диске", я попытался проверить, изменяется ли значение переменной 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.

3 ответа

Хорошо, я наконец понял это.

Используя Xcode 7.3.1..

Включение защиты файлов

  1. Включите защиту файлов с помощью вкладки "Возможности" в целевом приложении
  2. Если вы не хотите использовать NSFileProtectionComplete по умолчанию, измените этот параметр на портале разработчика под идентификатором приложения.
  3. Убедитесь, что XCode имеет новый профиль обеспечения, который он создает.
  4. Для защиты файлов, которые создает ваше приложение, и все.
  5. Чтобы защитить основные данные, вам нужно добавить опцию NSPersistentStoreFileProtectionKey: NSFileProtectionComplete в ваше постоянное хранилище.

Пример:

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]

Обратите внимание на пару "NSFileProtectionKey": 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
}

Когда вы нажимаете кнопку, этот код начинает выполнять метод checkFiles каждые 0,5 секунды. Это должно работать бесконечно с приложением на переднем плане или фоне - пока вы не заблокируете свой телефон. В этот момент он должен надежно выйти из строя примерно через 10 секунд - именно так, как описано в описании NSFileProtectionComplete.

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
        }
    }
    

Нам нужно понять, как работает защита данных. На самом деле, вам даже не нужно включать его. Начиная с iOS7, уровень защиты по умолчанию - "Защита файла завершена до первой аутентификации пользователя".

Это означает, что файлы не доступны, пока пользователь не разблокирует устройство в первый раз. После этого файлы остаются доступными даже тогда, когда устройство заблокировано и пока оно не выключится или не перезагрузится.

Другое дело, что вы будете видеть данные приложения на доверенном компьютере всегда - независимо от уровня защиты данных.

Тем не менее, данные не могут быть доступны, если кто-то пытается прочитать их непосредственно с флешки. Цель защиты данных состоит в том, чтобы гарантировать, что конфиденциальные данные не могут быть извлечены из хранилища устройства, защищенного паролем.

После запуска этого кода я все еще мог получить доступ и прочитать содержимое, записанное в protectedFileURL, даже после блокировки устройства.

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

Но это нормально, так как я запустил iExplorer на доверенном компьютере. По той же причине, если вы видите свой файл sqlite, то это нормально.

Ситуация меняется, если ваше устройство потеряно или украдено. Хакер не сможет прочитать файл sqlite, поскольку он зашифрован. Ну, если только он не угадает ваш пароль.

Вместо того, чтобы зашифровывать файл на локальном уровне, я устанавливаю NSFileProtectionComplete для приложения в целом.

Создайте файл 'authorlements.plist' в корневой папке приложений со следующим содержимым.

<?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>

Затем, если вы еще этого не сделали (это может быть проблемой с шифрованием на уровне файлов), включите защиту данных в своих приложениях.

Другие вопросы по тегам