Swift 정리 - Keychain

김세영·2022년 5월 6일
0

Doc - Article 정리

목록 보기
2/4

출처: Using the Keychain to Manage User Secrets - Apple Developer

키체인을 사용하여 사용자의 정보를 안전하게 관리하는 방법을 알아봅니다.

앱은 비밀번호, 토큰과 같은 민감한 정보에 접근해야 할 때가 있습니다. 매번 사용자에게 물어보는 것은 사용자 경험을 매우 해칠 것이고, 암호화하지 않고 저장하는 것은 심각한 위험이 될 수 있습니다.

키체인은 이 두 가지 문제를 모두 해결합니다.
아래 그림은 인터넷 비밀번호를 키체인에 저장하는 그림입니다.

위 그림에서 보다시피, 사용자에게 정보를 물어봐야 할 때만 사용자를 끌어들일(Involve) 수 있습니다.

SecItemAdd(_:_:)

사용자가 처음 비밀번호를 입력하고 인증이 완료되면 SecItemAdd 함수가 호출됩니다. 다음 접속 시 서버가 비밀번호를 요구하면, 키체인에서 해당 정보를 찾아서 바로 사용할 수 있습니다.

먼저 반환되는 OSStatus는 다음 문서에서 확인할 수 있습니다.
Security Framework Result Codes

파라미터는 다음과 같습니다.

attributes: CFDictionary

CFDictionary라는 타입을 요구합니다. Core Foundation 프레임워크에 있으며, "불변 딕셔너리 객체의 참조"라고 합니다.

대표적인 딕셔너리의 구성 요소는 다음과 같습니다.

  • 아이템 클래스
    kSecClass 키를 사용합니다. 이 키는 해당 정보가 어떠한 종류인지를 나타냅니다. (i.e. password, certificate, ...)
    Item Class Keys and Values

  • 데이터
    kSecValueData 키를 사용하여 저장하고 싶은 데이터를 가리킵니다(indicate). 키체인 서비스는 항목 중 하나라도 보안이 요구된다면 이를 암호화합니다.

  • 선택적 특성
    아이템을 찾거나 데이터를 공유 및 사용하는 방법을 나타내는 속성 키를 포함합니다. 각 아이템마다 고유한 속성이 있지만, 속성은 얼마든지 추가할 수 있습니다.
    Item Attribute Keys and Values

  • 선택적 반환 타입
    절차가 완료되었을 때 반환하고자 하는 데이터가 있는 경우, 이를 나타내는 반환 타입 키를 하나 이상 포함합니다. 해당 키가 없는 경우 SecItemAdd(_:_:) 의 반환 데이터를 무시합니다.
    Item Return Result Keys

result: UnsafeMutablePointer<CFTypeRef?>?

함수 반환 시, 새로 추가된 아이템의 참조입니다. 원하지 않을 경우 nil을 전달합니다. 그렇지 않을 경우 참조 객체 해제의 책임이 앱으로 넘어옵니다.

다시 다이어그램으로 돌아가 봅시다.
일반적인 앱의 흐름은 중앙의 흐름일 것입니다.
(Start -> Authenticate -> End)
이 과정에서 사용자와의 상호작용은 전혀 일어나지 않습니다.
이를 위해, 앱에서는 키체인에 저장된 비밀번호를 가져와야 합니다.

SecItemCopyMatching(_:_:)

SecItemCopyMatching(_:_:) 함수를 통해 키체인에서 원하는 정보를 가져올 수 있습니다.

반환값은 SecItemAdd(_:_:)와 동일한, OSStatus입니다.

파라미터는 다음과 같습니다.

query: CFDictionary

아이템을 검색할 떄 쿼리로 사용될 딕셔너리입니다.
대표적인 딕셔너리 구성 요소는 다음과 같습니다.

  • 아이템 클래스
    찾고자 하는 아이템의 종류를 나타냅니다.
    Item Class Keys and Values

  • 특성
    아이템이 반드시 가져야하는 속성을 정의하여 검색의 폭을 좁힐 수 있습니다. 많이 정의할수록 정교한 결과가 나오지만, 모든 속성이 모든 아이템 클래스에 적용되지는 않습니다.

  • 검색 파라미터
    결과로 받아오는 아이템의 수를 정하거나, 특정 집합에서만 아이템을 찾는 등의 검색 조건을 설정합니다.
    Search Attribute Keys and Values

  • 하나 이상의 반환 타입
    Item Return Result Keys에 있는 키를 사용하여 속성, 데이터, 데이터에 대한 참조 또는 이러한 조합을 검색할 것인지를 지정합니다. 두 개 이상 지정하면 각 타입이 포함된 딕셔너리가 반환됩니다.
    Multiple Results를 허용하면 아이템 배열 형태로 반환됩니다.

result: UnsafeMutablePointer<CFTypeRef?>?

함수가 반환될 때, 검색된 아이템들에 대한 참조입니다. 결과의 정확한 타입은 attributes에 지정된 값을 바탕으로 제공됩니다.

다이어그램의 왼쪽 시나리오에서는 정보가 변경되는 것을 다루고 있습니다.
이러한 시나리오에서는 키체인을 업데이트해 주어야 합니다.

SecItemUpdate(_:_:)

SecItemUpdate(_:_:) 함수를 통해 키체인 아이템을 업데이트할 수 있습니다.

반환값은 역시 동일하게 OSStatus입니다.
파라미터는 다음과 같습니다.

query: CFDictionary

업데이트할 키체인 아이템의 검색을 위한 딕셔너리입니다.

attributesToUpdate: CFDictionary

값이 변경되어야 하는 아이템의 속성과, 새 값이 포함된 딕셔너리입니다. "메타" 특성은 허용되지 않고, 실제 키체인 속성만 허용됩니다.

마지막으로, 사용자가 더 이상 비밀번호가 필요 없어질 수 있습니다. 이 때 앱은 중요한 정보들을 반드시 잊어버려야 합니다.

SecItemDelete(_:)

SecItemDelete(_:) 함수를 통해 키체인에서 아이템을 제거할 수 있습니다.

반환값은 역시 OSStatus입니다.
파라미터는 query 하나만 존재합니다.

query: CFDictionary

삭제하고자 하는 키체인 아이템을 찾을 때 사용되는 딕셔너리입니다.


사용을 안해볼 수 없죠

간단히 첫 접속 시 토큰을 입력하여 키체인에 저장하고, 다음 접속 시 키체인에서 토큰을 획득하여 자동 로그인을 하는 앱을 만들어 보겠습니다.

TokenService


싱글턴으로 사용하도록 하였고, 검색을 위해 keychainAccount 프로퍼티와 searchQuery 프로퍼티를 정의했습니다.


printDescription은 다음과 같습니다.

OSStatus에서 에러메시지가 무슨 뜻인지 확인하고 싶을 때는
SecCopyErrorMessageString(_:_:)을 사용하라고 합니다.



저장할 때의 쿼리는 searchQuerykSecValueData, 즉 저장할 데이터를 추가한 형태인 것을 알 수 있습니다.



업데이트 쿼리는 딱 데이터만 담고 있고,
[검색 쿼리로 아이템 찾고 -> 업데이트 쿼리의 데이터로 교체]
의 형태입니다.



쿼리는 searchQuery + 검색할 아이템 개수 + 반환할 데이터 및 속성 추가

item은, 타입에도 Ref가 있듯이, 찾은 아이템을 참조 합니다.
따라서 위의 문서에 나온 것처럼 "앱이 참조 객체의 해제를 책임"져야 합니다.
프로퍼티라면 deinit될 때 잘 해제되는지 확인하라는 듯 합니다.

[쿼리로 아이템 검색 -> item에 참조 추가]
의 형태입니다.

let data = existingItem[kSecValueData as String] as? Data
위(add, update)에서 계속 나왔던, kSecValueData를 꺼내주는 작업입니다.



너무 간단;;
쿼리로 아이템만 검색하면 지워집니다.

UI


대충... 대충.. 만들어봤습니다..

  • 대충 기본 화면
    앱이 켜지면 대충 기본 화면이 표시되고, 이 화면의 뷰 컨트롤러에서 키체인에 저장된 토큰을 불러옵니다.
    토큰이 없다면 -> 대충 로그인 화면
    토큰이 있다면 -> 대충 로그인 후 화면

  • 대충 로그인 화면
    TextField에 토큰을 입력하고 [로그인] 버튼을 누르면 키체인에 토큰을 저장합니다.
    성공하면 대충 로그인 후 화면으로 이동합니다.

  • 대충 로그인 후 화면
    [토큰 보기] 버튼을 누르는 동안 토큰이 표시됩니다. (원래 이러면 안되지만)
    [로그아웃] 버튼을 누르면, 다음과 같습니다.

    • 자동 로그인으로 이 화면으로 왔다면
      dismiss 후 대충 로그인 화면을 present

    • 직접 로그인하여 이 화면으로 왔다면
      그냥 dismiss

RootViewController


present... 메서드에서 알 수 있듯, signedInsignIn 뷰를 present하는 메서드들입니다. 따라서 viewDidAppear(_:)에서 호출하는 모습입니다.

searchToken()은 토큰을 불러오면 String을 반환하고, 불러오지 못하면 nil을 반환합니다. 그 차이를 이용해 뷰를 각각 present합니다.

SignInViewController


tokenField에 토큰을 입력하고, 버튼을 눌러 토큰을 저장합니다.
저장에 성공하면 true를 반환하기 때문에 성공 시 signedIn을 present할 수 있습니다.

SignedInViewController


touchDowntouchUpInside 액션으로, 버튼이 눌리고 있을 때만 토큰을 보여주는 기능을 넣어봤습니다.

[로그아웃] 버튼이 눌리면 deleteToken()을 호출하고,

이 행동을 하게 됩니다.

정리

  • 생성: SecItemAdd(_:_:)
    쿼리에는 저장할 데이터와, 그 데이터를 찾을 수 있는 속성, 데이터의 종류를 알맞게 넣어야 한다.

  • 검색: SecItemCopyMatching(_:_:)
    쿼리에는 아이템을 찾을 수 있는 속성을 넣어야 하고, 얻고 싶은 데이터 및 속성을 선택할 수 있다.
    아이템을 찾으면 두 번째 인자가 아이템을 참조한다.

  • 수정: SecItemUpdate(_:_:)
    검색 쿼리에는 아이템을 찾을 수 있는 속성을 넣어야 하고, 데이터 쿼리에는 변경할 데이터를 넣어야 한다.
    아이템을 찾으면 데이터 쿼리에 있는 데이터로 아이템을 변경한다.

  • 삭제: SecItemDelete(_:)
    검색 쿼리에 아이템을 찾을 수 있는 속성을 넣어야 한다.
    찾으면 그 아이템을 지운다.

키체인을 너무 어렵게 생각했는데 의외로 금방 이해가 되었네요...!

profile
초보 iOS 개발자입니다ㅏ

0개의 댓글