티스토리 뷰

값을 저장할때 UserDefault 에 저장을 하는 경우가 많습니다.

특히나 간단한 데이터의 경우에는 더더욱 많이 사용하게 되는데요.

 

만약 정말 중요한 데이터라면 어떨까요? 

이 경우 KeyChain 저장 방법을 많이 사용하게 됩니다. 

 

UserDefault 가 단순하게 앱 내부에 key- value 형식으로 저장된다면 

KeyChain 은 기본적으로 암호화되어 값을 저장하고 시스템 (앱 내부 X)에서 관리 된다고 합니다. 

 

그래서 오늘은 KeyChain 저장법을 알아볼까합니다. 

 

UserDefault 저장법이 궁금하신 분들은 아래 포스트를 참고해주세요.

2022.12.07 - [iOS개발/Swift 기본] - Swift UserDefaults로 간단한 데이터 저장하기

 

 

특징

  • UserDefault 에 비해 설정하는 방법 / 꺼내오는 방법이 복잡한 편입니다.
  • 대부분의 액션 (저장/검색/수정/삭제) 후에는 OSStatus 의 형태로 성공/실패 결과를 보여줍니다. 
  • 사용을 위해서는 Securityimport 해야 사용가능합니다. 
  • 앱 내부가 아닌 개발자의 계정의 영향을 받는다고 합니다. 
  • 그러다보니 동일 개발자 계정의 경우 서로 데이터 공유가 가능하다고 합니다. 
  • 모든 데이터는 암호화되서 저장된다고 합니다.

import 방법 

- Swift

import Security

- Obj-C

#import <Security/Security.h>

 

알아두어야 하는 형식 (CFDictionary)

KeyChain 의 경우 일반적으로 Key가 Dictionary 형식으로 되어있다고 생각하시면 편합니다.

일반적으로 아래와 같은 CFDictionary 를 생성하고 사용합니다.  

데이터 종류 / 서비스 이름 / 설정한 유저 계정 / 데이터 접근 가능 여부 / 실제 데이터 등의 속성을 가집니다. 

(사용할때 필요에 따라 수정해서 넣습니다.)

- Swift

let saveData : CFDictionary = [kSecClass : kSecClassGenericPassword,
                            kSecAttrService : "PlayGround" ,
                            kSecReturnData : true ,
                            kSecValueData : password.data(using: .utf8)!] as CFDictionary

- Obj-C

NSDictionary *saveData = @{
   (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
   (__bridge id)kSecAttrService: @"Playgound",
   (__bridge id)kSecReturnData: @YES,
   (__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
};

// 후에 사용시 (__bridge CFDictionaryRef) 로 형식을 바꾸어 사용합니다.

 

각 속성별 의미 

  • kSecClass : 값의 종류 (ex. 인증서,비밀번호, 인터넷 비밀번호, 특징)
  • kSecAttrService : 값을 저장할 서비스 이름 (UserDefault의 Key 와 유사합니다.)
  • kSecAttrAccount : 값을 저장할 계정 이름 (검색할때 사용될 Key 중 하나 이지만 이번예제에서는 생략했습니다.) 
  • kSecReturnData : 값을 꺼내볼수있는지에 대한 여부 (설정하지 않은 경우 값을 못가져오는 경우가 있습니다.)
  • kSecValueData : 실제 저장할 데이터

특히 kSecClass의 경우 종류가 다양하니 반드시 자신에게 맞는 형식을 확인하고 사용해주세요!

(여기서 서비스이름은 예제로 "PlayGround"를 넣어놨습니다. 알맞게 변경 후 사용해주세요.)

 

OSStatus 종류 

자주쓰이는 두가지만 알아볼께요.

  • errSecSuccess : 성공했을경우
  • errSecDuplicateItem : 이미 저장된 값인 경우 (주로 값 추가할때 많이 발생)

 

이제 사용법을 알아봅시다. 

사용 방법

저장 

- Swift

func addKeyChainValue(password : String) -> OSStatus {
    let saveData : CFDictionary = [kSecClass : kSecClassGenericPassword,
                                    kSecAttrService : "PlayGround" ,
                                    kSecReturnData : true ,
                                    kSecValueData : password.data(using: .utf8)!] as CFDictionary
    return SecItemAdd(saveData, nil)
    // 저장했으면 정상적인 값이 리턴됩니다. errSecSuccess
}

 

- Obj-C

- (OSStatus)addKeyChainValue : (NSString*) password {
    NSDictionary *saveData = @{
       (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
       (__bridge id)kSecAttrService: @"Playgound",
       (__bridge id)kSecReturnData: @YES,
       (__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
    };
    return SecItemAdd((__bridge CFDictionaryRef)saveData, NULL);
}

 

값 수정 

- Swift

func changeKeyChainValue(password : String) -> OSStatus {
    let savedData : CFDictionary = [kSecClass : kSecClassGenericPassword,
                                    kSecAttrService : "PlayGround"] as CFDictionary

    let updateData : CFDictionary = [kSecValueData : password.data(using: .utf8)!] as CFDictionary
    //savedData 장소의 값을 updateData로 수정하기
    return SecItemUpdate(savedData, updateData )
}

- Obj-C

- (OSStatus)changeKeyChainValue : (NSString*) password {
    NSDictionary *savedData = @{
       (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
       (__bridge id)kSecAttrService: @"Playgound",
    };
    NSDictionary *updateData = @{
       (__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
    };
    return SecItemUpdate((__bridge CFDictionaryRef)savedData, (__bridge CFDictionaryRef)updateData);
}

 

값 출력 

- Swift

func searchKeyChainValue() -> String? {
    let savedData : CFDictionary = [kSecClass : kSecClassGenericPassword,
                                    kSecAttrService : "PlayGround" ,
                                    kSecReturnData : true ] as CFDictionary
    var searchWord :CFTypeRef? = nil
    let searchResult = SecItemCopyMatching(savedData, &searchWord)
    if(searchResult != errSecSuccess){
        return nil // 없으면 미출력
    }
    let searchData : Data = searchWord as! Data
    // 값 출력 
    return String(data: searchData, encoding: .utf8)
}

- Obj-C

- (NSString*)searchKeyChainValue{
    NSDictionary *savedData = @{
           (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
           (__bridge id)kSecAttrService: @"Playgound",
           (__bridge id)kSecReturnData: @YES,
	};
    CFTypeRef searchWord;
  	OSStatus searchResult = SecItemCopyMatching((__bridge CFDictionaryRef)savedData, &searchWord);
    if(searchResult == errSecSuccess){
    	NSData *licenseKeyData = (__bridge_transfer NSData *)searchWord;
   	    NSString *searchData = [[NSString alloc] initWithData:licenseKeyData encoding:NSUTF8StringEncoding];
  		return searchData;
  	}
    return NULL;
}

 

데이터 삭제 

- Swift

func removeKeyChainValue() -> OSStatus {
	// 저희는 서비스 이름으로 저장되있으니 서비스 이름으로 지우면 됩니다. 
    let savedData : CFDictionary = [kSecClass : kSecClassGenericPassword,
                                    kSecAttrService : "PlayGround" ,
                                    kSecReturnData : true ] as CFDictionary
    return SecItemDelete(savedData)
}

- Obj-C

- (OSStatus)removeKeyChainValue {
    NSDictionary *savedData = @{
       (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
       (__bridge id)kSecAttrService: @"Playgound",
       (__bridge id)kSecReturnData: @YES
    };
    return SecItemDelete((__bridge CFDictionaryRef)savedData);
}

 

자료를 찾다보니 swift 는 많은데 Obj-C는 자료가 많이 없더라고요.

필요하신 분들에게 도움이 되면 좋겠네요

 

오늘도 파이팅입니다. 

 

 

댓글