STUDYING/iOS

[iOS] KeyChain이란~? Swift코드를 통해 살펴보기

EOZIN 2021. 1. 6. 19:18
728x90

iOS 앱 개발 프로젝트를 할 때 민감한 정보를 어디에 저장해야할지 고민이됐다.
UserDefaults보다 보안이 뛰어난 키체인을 사용해보자!

 

UserDefaults

UserDefaults에도 데이터를 쉽게 저장할 수 있지만, 단순히 .info 파일에 키-값 쌍을 텍스트 형태로 저장하기 때문에 OS를 탈옥하면 내용물을 볼 수 있다는 문제가 있다.

보안이 필요한 데이터에는 적합하지 않다.

 

KeyChain

이를 방지하기 위해 암호나 API Token과 같은 민감한 정보는 KeyChain에 저장하는 것이 좋다.

 

KeyChain이란~~?

Apple이 제공하는 보안 프레임워크

Keychain은 디바이스 안에 암호화된 데이터 저장 공간을 의미한다. 사용자는 암호화된 공간에 데이터를 안전하게 보관할 수 있다.

무엇을 저장할까?

  • 로그인 및 암호 (해시)
  • 결제 데이터
  • 알고리즘 암호화를위한 키

등등... 단순한 구조의 비밀을 유지하고 싶은 것들을 저장

 

KeyChain 특징

  • 사용자가 직접 제거하지 않는 이상, 앱을 제거하고 설치해도 데이터는 남아있다.
  • 디바이스를 Lock하면 KeyChain도 잠기고, 디바이스를 UnLock하면 KeyChain역시 풀린다.
    • KeyChain이 잠긴 상태에서는 Item들에 접근할수도, 복호화 할 수도 없다.
    • KeyChain이 풀린 상태에서도 해당 Item을 생성하고 저장한 어플리케이션에서만 접근이 가능하다.
  • 같은 개발자가 개발한 여러 앱에서 키체인 정보를 공유할 수 있다.

 

KeyChain Service API

KeyChain service API로 민감한 데이터를 암호화 - 복호화 하며 재사용 하는 행위를 보다 쉽고 안전하게 사용할 수 있게한다.

KeyChain Items

키체인에 저장된 Data 단위. 키체인은 하나 이상의 Keychain item을 가질 수 있다.

 

KeyChain Item Class

저장할 Data의 종류.

  • kSecClassInternetPassword
  • kSecClassCertificate
  • kSecClassGenericPassword
  • kSecClassIdentity
  • kSecClassKey

대표적으로 인터넷용 아이디/패스워드르 저장할 때 사용하는 kSecClassInernetPassword,

인증서를 저장할 때 사용하는 kSecClassCertificate,

일반 비밀번호를 저장할 때 사용하는 kSecClassGenericPassword등이 있다.

 

Attributes

keychain item을 설명하는 특성. item class에 따라 설정할 수 있는 attributes가 달라진다.

애플 문서를 보면 아래와 같이 각 아이템 클래스별로 다양한 어트리뷰트가 존재한다.

 


 

구현해보기

Token구조체 배열을 KeyChain으로 저장하자!


일반 비밀번호를 저장할 때 사용하는 kSecClassGenericPassword 라는 Keychain Item Class 를 사용하여 TOTP토큰배열을 저장하는 과정에 대해 살펴보도록 하겠다.

 

KeyChain Query 작성하기

----------------------------------------------------------------
kSecClass: 키체인 아이템 클래스 타입,
kSecAttrService: 서비스 아이디, // 앱 번들 아이디
kSecAttrAccount: 저장할 아이템의 계정 이름,
kSecAttrGeneric: 저장할 아이템의 데이터
----------------------------------------------------------------

private let account = "TokenService"
private let service = Bundle.main.bundleIdentifier

let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                              kSecAttrService: service,
                              kSecAttrAccount: account,
                              kSecAttrGeneric: data]

 

API 메소드

Create

  • SecItemAdd(_:_:)
  • 키체인에 하나 이상의 항목을 추가할 때 사용
func createTokens(_ token: [Token]) -> Bool {
    guard let data = try? JSONEncoder().encode(token),
          let service = self.service else { return false }

    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                  kSecAttrService: service,
                                  kSecAttrAccount: account,
                                  kSecAttrGeneric: data]

    return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}

Read

  • SecItemCopyMatching(_:_:)
  • 검색 쿼리와 일치하는 키체인 항목을 하나 이상 반환하는 기능. 또한, 특정 키 체인 항목의 속성을 복사할 수 있다.
func readTokens() -> [Token]? {
    guard let service = self.service else { return nil }
    let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                  kSecAttrService: service,
                                  kSecAttrAccount: account,
                                  kSecMatchLimit: kSecMatchLimitOne,
                                  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[kSecAttrGeneric as String] as? Data,
          let tokens = try? JSONDecoder().decode([TOTPToken].self, from: data) else { return nil }

    return tokens
}

Update

  • SecItemUpdate(_:_:)
  • 검색 쿼리와 일치하는 항목을 수정할 수 있는 기능
func updateTokens(_ token: [Token]) -> Bool {
    guard let query = self.query,
          let data = try? JSONEncoder().encode(token) else { return false }

    let attributes: [CFString: Any] = [kSecAttrAccount: account,
                                       kSecAttrGeneric: data]

    return SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == errSecSuccess
}

Delete

  • SecItemDelete(_:)
  • 검색 쿼리와 일치하는 항목을 제거하는 기능
func deleteTokens() -> Bool {
    guard let query = self.query else { return false }
    return SecItemDelete(query as CFDictionary) == errSecSuccess
}

 

참고 블로그

 

Keychain Tutorial (키체인 튜토리얼)

Keychain Services API Tutorial for Passwords in Swift 왜 키체인을 사용하는가? 어차피 Userdefaults에 저장하는 것도 base64 encoding 하지않는가? 하지만 공격자가 그런것을 복구하는 것은 어려운 일이 아니..

kor45cw.tistory.com

 

기본 iOS 보안: 키체인과 해싱 (Basic iOS Security: Keychain and Hashing)

[마지막 수정일 : 2018.03.09] 원문 : https://www.raywenderlich.com/185370/basic-ios-security-keychain-hashing 기본 iOS 보안: 키체인과 해싱 (Basic iOS Security: Keychain and Hashing) 소프트웨어 개발..

kka7.tistory.com

 

키체인 간단 사용법

키체인에 대해 알아보고, 구현해보는 글입니다

daeun28.github.io