티스토리 뷰

Swift 로 iOS 개발 시 다양한 데이터를 저장해야 하는 경우가 있습니다. 이 글에서는 UserDefaults 와 Keychain 을 통해 데이터를 저장하는 방법을 알아보도록 하겠습니다.

 

1. UserDefaults

UserDefaults 란, 사용자의 기본 데이터베이스에 대한 인터페이스로, 앱 실행시 키-값 쌍을 지속적으로 저장합니다. UserDefaults 클래스는 기본 시스템과 상호 작용하기위한 프로그래밍 인터페이스를 제공합니다. 이를 사용하면 앱은 사용자의 기본 데이터베이스에있는 매개 변수 집합에 값을 할당하여 앱의 기본 환경 설정을 저장합니다. 매개 변수는 일반적으로 시작시 앱의 기본 상태 또는 기본적으로 작동하는 방식을 결정하는 데 사용되기 때문에 기본값이라고합니다. 저장할 수 있는 타입 NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary 입니다.

 

간단한 사용 방법을 살펴보면, 다음과 같습니다.

// 데이터 저장하기
UserDefaults.standard.set("LoggedIn", forKey: "status")

// 데이터 가져오기
let status = UserDefaults.standard.string(forKey: "status") // 저장한 값에 따라 메소드가 다름. 예) Bool 타입이 저장된 경우 .bool(forKey:)

 

UserDefaults 를 사용하면 간단한 사용자 설정(알림 설정 on/off 등) 등을 저장할 수 있으며 이 데이터는 앱이 삭제될 때 같이 삭제됩니다. UserDefaults 로 저장되는 데이터는 plist 에 저장되기 때문에 보안에 취약합니다.

 

developer.apple.com/documentation/foundation/userdefaults

 

Apple Developer Documentation

 

developer.apple.com

 

2. Keychain

Keychain 을 사용하면 데이터를 안전하게 보관할 수 있습니다. Keychain 에 데이터를 저장하기 위해서는 Keychain Item 으로 패키징해야 합니다. Keychain Item 은 데이터 자체와 함께 항목의 접근성을 제어하고 검색 가능하게 만들기 위해 공개적으로 표시되는 속성 집합을 제공합니다. 키 체인 서비스는 디스크에 저장된 암호화 된 데이터베이스인 키 체인에서 데이터 암호화 및 저장 (데이터 속성 포함)을 처리합니다. 나중에 승인 된 프로세스는 키 체인 서비스를 사용하여 항목을 찾고 데이터를 해독합니다.

 

사용하는 방법을 살펴보면 저장할 데이터와 속성집합을 딕셔너리 형태의 Keychain Item 으로 만들고 func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus 를 사용하여 데이터를 저장합니다. 저장한 데이터를 읽을 때는 func SecItemCopyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus 를 사용합니다. 

import UIKit
import Security

class Keychain: NSObject {
    /*
     * 외부로 제공되는 메소드
     * serviceIdentifier: 키체인에서 해당 앱을 식별하는 값으로 앱만의 고유한 값을 써야합니다. (데이터를 해당 앱에서만 사용하기 위해)
     * userAccount: 앱 내에서 데이터를 식별하기 위한 키에 해당하는 값입니다.
     */
         
    public class func saveData(serviceIdentifier:NSString, userAccount:NSString, data: String) {
        self.save(service: serviceIdentifier, userAccount: userAccount, data: data)
    }
     
    public class func loadData(serviceIdentifier:NSString, userAccount:NSString) -> String? {
        let data = self.load(service: serviceIdentifier, userAccount: userAccount)
         
        return data
    }
    
    /*
     * Keychain 에 실제 접근하는 내부 메소드
     */
         
    private class func save(service: NSString, userAccount:NSString, data: String) {
        let dataFromString: Data = data.data(using: String.Encoding.utf8)!
         
        // Instantiate a new default keychain query
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: userAccount,
                                    kSecValueData as String: dataFromString]
        
        // Delete any existing items
        SecItemDelete(query as CFDictionary)
         
        // Add the new keychain item
        SecItemAdd(query as CFDictionary, nil)
    }
     
    private class func load(service: NSString, userAccount:NSString) -> String? {
        // Instantiate a new default keychain query
        // Tell the query to return a result
        // Limit our results to one item
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: userAccount,
                                    kSecReturnData as String: kCFBooleanTrue!,
                                    kSecMatchLimit as String: kSecMatchLimitOne as String]
         
        var retrievedData: NSData?
        var dataTypeRef:AnyObject?
        var contentsOfKeychain: String?
         
        let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
         
        if (status == errSecSuccess) {
            retrievedData = dataTypeRef as? NSData
            contentsOfKeychain = String(data: retrievedData! as Data, encoding: String.Encoding.utf8)
        }
        else
        {
            print("Nothing was retrieved from the keychain. Status code \(status)")
            contentsOfKeychain = nil
        }
         
        return contentsOfKeychain
    }
}

 

Keychain Item 으로 패키징하는 과정에서 해당하는 속성을 사용할 때 계속 문자열로 형변환을 해주는 부분이 반복됩니다. 이 부분을 개선하고 싶다면 다음과 같이 상수로 만들어 사용할 수 있습니다.

// ...
// Keychain 관련 쿼리 키 값들
let kSecClassValue                  = NSString(format: kSecClass)
let kSecAttrAccountValue            = NSString(format: kSecAttrAccount)
let kSecValueDataValue              = NSString(format: kSecValueData)
let kSecAttrGenericValue            = NSString(format: kSecAttrGeneric)
let kSecAttrServiceValue            = NSString(format: kSecAttrService)
let kSecAttrAccessValue             = NSString(format: kSecAttrAccessible)
let kSecMatchLimitValue             = NSString(format: kSecMatchLimit)
let kSecReturnDataValue             = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue          = NSString(format: kSecMatchLimitOne)
let kSecAttrAccessGroupValue        = NSString(format: kSecAttrAccessGroup)
let kSecClassGenericPasswordValue   = NSString(format: kSecClassGenericPassword)

class Keychain: NSObject {
    // ... 
    private class func save(service: NSString, userAccount:NSString, data: String) {
        // ...
        let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], 
        			forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
         
        // ...
    }
     
    private class func load(service: NSString, userAccount:NSString) -> String? {
        // ...
        let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue!, kSecMatchLimitOneValue], 
        			forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
        // ...
    }
}

 

developer.apple.com/documentation/security/keychain_services/keychain_items

 

Apple Developer Documentation

 

developer.apple.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함