앱이 생성하는 파일에 적용되는 암호화 수준을 정의하여 사용자 데이터를 안전하게 보호하는 iOS의 핵심 보안 기능
애플의 기기들은 새로운 파일이 생성되면 그 파일을 생성한 앱이 각 파일에 클래스를 할당한다.
각 클래스는 데이터 접근 시기를 결정하는 정책, 기능이 다르다.
✨ 여기서 설명하는 클래스는 프로그래밍 관점에서의 클래스가 아닌 등급, 범주의 의미!
가장 강력한 보호 수준
기기가 잠금 해제된 상태에서만 파일에 접근 가능
기기가 잠기면 즉시 접근이 불가능해짐.
사용자가 메일 앱에서 대용량 첨부파일 다운로드를 시작
다운로드가 진행되는 동안 사용자가 기기를 잠굼.
메일 앱은 백그라운드에서 계속 실행되다가, 잠기는 순간, 파일에 대한 쓰기 권한이 막혀 중단
앱이 파일 작업을 진행하고 있는 동안만 보호
기기가 잠겨 있어도 앱이 이미 파일을 열고 있었다면 계속 접근 가능
새 파일을 생성하고 쓰는 것도 가능
사용자가 메일 앱에서 대용량 첨부파일 다운로드를 시작
다운로드가 진행되는 동안 사용자가 기기를 잠굼.
메일 앱은 백그라운드에서 계속 실행되면서 다운로드된 데이터를 임시 파일에 계속 씀. 이때 새로운 데이터 조각을 파일에 계속 쓸 수 있음.
재부팅 후 첫 잠금 해제 전까지 보호
사용자가 기기를 재부팅한 후 처음으로 잠금을 해제할 때까지 파일이 보호
그 이후에는 기기가 잠겨도 접근 가능
✨ 파일 저장 시 기본으로 적용되는 설정
보호 없음 (None)
FileManager를 사용하여 파일을 생성하거나 수정할 때 보호 클래스 설정
import Foundation
let fileManager = FileManager.default
guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let sensitiveFileURL = documentsURL.appendingPathComponent("SensitiveData.txt")
let content = "This is highly sensitive information."
do {
// NSFileProtectionComplete 수준으로 파일 생성
try content.write(to: sensitiveFileURL, atomically: true, encoding: .utf8)
var attributes = [FileAttributeKey: Any]()
attributes[.protectionKey] = FileProtectionType.complete
try fileManager.setAttributes(attributes, ofItemAtPath: sensitiveFileURL.path)
print("민감한 파일이 성공적으로 저장되었습니다.")
} catch {
print("파일 저장 중 오류 발생: \(error)")
}
attributes[.protectionKey] = FileProtectionType.complete
해당 부분으로 파일의 보호 클래스를 설정
파일을 생성, 덮어쓰기, 속성 변경 등의 변경에 클래스 설정 및 변경 가능
Core Data 스택을 설정할 때
NSPersistentStoreDescription의 옵션으로 보호 수준을 지정
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "YourAppName")
// ---------------------> 여기가 핵심! <---------------------
// 1. 컨테이너의 기본 설정(Description)을 가져옴
guard let description = container.persistentStoreDescriptions.first else {
fatalError("Failed to retrieve a persistent store description.")
}
// 2. 원하는 데이터 보호 클래스를 옵션으로 설정
// FileProtectionType.complete = NSFileProtectionComplete
description.setOption(FileProtectionType.complete as NSObject,
forKey: NSPersistentStoreFileProtectionKey)
// -----------------------------------------------------------
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
container에 저장되는 데이터베이스 파일들은 container에 설정된 보호 클래스로 관리됨.
다만 한 번 설정된 container는 보호 클래스 변경이 어려우므로, 새로운 container에 마이그레이션이 필요함.
Keychain에 데이터를 저장할 때는
kSecAttrAccessible속성을 사용하여 접근성을 제어
import Security
let account = "com.example.myApp.user"
let password = "mySecretPassword".data(using: .utf8)!
// 저장할 쿼리를 생성
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: password,
// 기기가 잠금 해제되었을 때만 접근 가능하도록 설정
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
// 키체인에 아이템 추가
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecSuccess {
print("키체인에 안전하게 저장되었습니다.")
} else if status == errSecDuplicateItem {
print("이미 해당 아이템이 존재합니다.")
// 업데이트 로직을 추가할 수 있습니다.
} else {
print("키체인 저장 오류: \(status)")
}
kSecAttrAccessible으로 보안 수준 설정 가능
kSecAttrAccessibleWhenUnlocked: 기기가 잠금 해제된 동안에만 접근 가능 (NSFileProtectionComplete와 유사)
kSecAttrAccessibleAfterFirstUnlock: 재부팅 후 첫 잠금 해제 이후부터 접근 가능 (NSFileProtectionCompleteUntilFirstUserAuthentication과 유사)
kSecAttrAccessibleAlways: 항상 접근 가능 (NSFileProtectionNone과 유사)
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: 암호가 설정된 이 기기에서만, 그리고 잠금 해제 시에만 접근 가능
참고
https://support.apple.com/ko-kr/guide/security/secb010e978a/web
https://developer.apple.com/documentation/uikit/encrypting-your-app-s-files