ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] KeyChain이란~? Swift코드를 통해 살펴보기
    STUDYING/iOS 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

     

Designed by Tistory.