[iOS] Keychain 파헤치기!

Hoojeong Kim·2022년 7월 11일
0

iOS의 데이터 저장

목록 보기
3/3
post-thumbnail

금요일에 쓴 글이 날아가버렸다.. 그래서 다시 쓰는 중인데.. 너무 슬프다.
월요일인 것도 화나는데


Keychain

먼저 애플 공식 문서를 살펴보자.

어... 도와줘요 파파고

보잘 것 없는 영어 실력으로 정리해보자면,

키체인은 비밀번호나 신용카드 정보, 인증서 등과 같은 민감한 데이터를 저장하는 데이터베이스이다.
데이터는 암호화되어 저장되며, 암호화한 데이터에 쉽게 접근할 수 있도록 도와준다.

키체인은 하나의 암호화된 컨테이너이며 Keychain services API는 민감한 데이터를 암호화하고 복호화하여 재사용하는 행위를 쉽고 안전하게 할 수 있도록 한다.

기본적으로 디바이스를 잠그면 키체인도 잠기고, 디바이스를 열면 키체인도 풀린다. 이렇게 키체인이 잠긴 상태에서는 데이터에 접근하는 것이 불가능하다.


Keychain Item

키체인에는 민감하고 중요한 데이터를 저장한다고 했는데, 어떻게 저장할까?

저장할 데이터는 Keychain Item으로 패키징하여 저장된다.

데이터를 암호화하여 item으로 패키징하는데, 이때 Attribute도 함께 저장한다.
이 attribute는 데이터에 접근하거나 검색하는 것을 가능하게 한다.

이때 attribute는 직접적으로 접근하게 하는 것이 아님. 해당 데이터의 속성이나 특징을 보여주는 것이지, 접근하는 것은 접근 제어에 해당하는 프로세스만 가능함.
즉, 접근 가능성을 보여주는 것임.


Using the Keychain to Manage User Secrets

앱은 비밀번호화 같은 민감한 사용자 데이터에 접근해야 하는 경우가 많은데, 데이터를 암호화하지 않고 저장하면 보안 위험이 따른다.

키체인은 암호화된 저장소에 쉽게 접근할 수 있도록 하여 이러한 문제를 해결한다. 다음과 같은 프로세스를 고려해보자.

먼저 item을 조회했을 때, 암호화된 데이터가 없다면 사용자가 입력한 정보가 유효한지 인증한 후에 키체인 item으로 저장한다. 만약 암호화된 데이터가 있다면, 그 정보가 유효한지 확인한다.

만약 유효할 경우에는 키체인 로그인 과정이 끝나게 된다. 만약 유효하지 않다면 사용자가 정보를 다시 입력하도록 하여 정보가 유효한지 검사한 후에 키체인 아이템을 업데이트 한다.


Keychain and SandBox

기본적으로 앱은 자기 자신의 키체인에만 접근할 수 있다.
또한 키체인에 저장된 정보는, 앱을 삭제하더라도 사라지지 않는다.

엥 어떻게요?
바로 여기서, 전에 공부했던 SandBox 개념이 등장한다.

모르는 사람은 [iOS] SandBox를 알아보자! 참고하기.

SandBox에 대해 짧게 정리해보자면,

앱이 외부로부터 공격을 받았을 때, 그 피해를 최소화하기 위해 고안된 모델이다. 애플의 모든 앱은 각각 하나의 SandBox로 감싸져 있고, 각 SandBox 안에 앱의 bundle나 데이터가 저장된다.
( UserDefaults도 이 SandBox 안에 저장된단 말씀!! )


아무튼 키체인은 이 SandBox의 밖에 저장되기 때문에 백날 앱을 지워봐야 지워지지 않는다.

위에서 키체인은 자기 자신의 키체인에만 접근할 수 있다고 설명했는데, 키체인 그룹을 사용하면 서로 다른 앱에서도 데이터를 공유할 수 있다.

Struct of Keychain

키체인을 구성하는 요소는 총 3개로 나눌 수 있다.

Keychain

키체인에 저장되는 데이터로, 키체인은 여러 개의 키체인 item을 가질 수 있다.

Item Class

키체인 item에 저장되는 데이터의 종류를 지정할 수 있다.

  • kSecClassGenericPassword
    • kSecAttrService: 키체인 아이템과 연관되어 있는 서비스의 이름
    • kSecAttrAccount: 저장할 아이템의 계정 이름 (아이디)
    • kSecAttrGeneric: 저장할 아이템의 데이터 (비밀번호)
  • kSecClassInternetPassword
    • kSecAttrAccount: 저장할 아이템의 계정 이름 (아이디)
  • kSecClassCertificate
  • kSecClassKey
  • kSecClassIdentity

Attribute

item 클래스에 대한 속성을 저장할 수 있다.

  • kSecMatchLimit
    • 키의 값을 저장할 경우, 이 값은 반환하거나 다른 조치를 취할 최대 결과 수를 지정
    • kSecMatchLimitOne
    • kSecMatchLimitAll
  • kSecReturnAttributes
    • 키의 값으로 true를 저장할 경우, CFDictionary 타입으로 반환
  • kSecReturnData
    • 키의 값으로 true를 저장할 경우, CFData 타입으로 반환

Using the Keychain

Add

SecItemAdd() 함수를 사용해 키체인 아이템을 생성한다.

public func add(_ value: String, forKey: String) {
        
	let account = forKey
    let password = value.data(using: String.Encoding.utf8)!
    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
    							  kSecAttrAccount: account,
                                  kSecValueData: password]
        
    let status = SecItemAdd(query as CFDictionary, nil)
    switch status {
    // 성공
    case errSecSuccess:
        print"\(forKey) add successful")
    // 키가 중복값이 있음
    case errSecDuplicateItem:
        update(password, forKey: account)
    // 실패
    default:
		print("\(forKey) add failed")
    }
}

Matching

SecItemCopyMatching() 함수를 사용해 키체인 아이템을 검색한다.

public func read(forKey: String) -> String {
        
	let account = forKey
    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
    							  kSecAttrAccount: account,
                                  kSecReturnAttributes: true,
                                  kSecReturnData: true]
        
    var item: CFTypeRef?
    if SecItemCopyMatching(query as CFDictionary, &item) != errSecSuccess { return nil }
        
    guard let existingItem = item as? [String: Any],
    	  let data = existingItem["v_Data"] as? Data,
    	  let password = String(data: data, encoding: .utf8) else { return nil }
        
	return password
}

Update

SecItemUpdate() 함수를 사용해 키체인 아이템을 수정한다.

private func update(_ value: Any, forKey: Any) {
        
	let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
										  kSecAttrAccount: forKey]
	let updateQuery: [CFString: Any] = [kSecValueData: value]
        
    let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
    switch status {
    // 성공
    case errSecSuccess:
    	print("\(forKey) update successful")
    // 실패
    default:
    	print("\(forKey) update failed")
    }
}

Delete

SecItemDelete() 함수를 사용해 키체인 아이템을 삭제한다.

public func delete(forKey: String) {
        
	let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
    									kSecAttrAccount: forKey]
        
    let status = SecItemDelete(deleteQuery as CFDictionary)
    switch status {
    // 성공
    case errSecSuccess:
    	print("\(forKey) remove successful")
    // 실패
    default:
    	print("\(forKey) remove failed")
    }
}


두번째 작성하다보니,,, 내가 얼마나 이해했는지 확인할 수 있어서 좋았던 듯?
응.. 그것만..

앞으로 저장 잘 해야징! (찡긋)

profile
나 애기 개발자 👶🏻

0개의 댓글