ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • iOS [Swift] - Keychain 정리
    iOS Develop 2023. 2. 14. 16:00
    반응형

    📄

    Keychain 개념

    • Apple에서 공식적으로 제공하고 있는 보안 프레임워크
    • 암호화된 Data Base
    • 사용자의 민감한 데이터를 저장
    • UserDefaults는 App이 삭제되면 같이 삭제되지만, Keychain은 그와 반대로 App이 삭제돼도 데이터 유지
    • Keychain은 중복된 값을 새로운 값이 자동으로 덮어씌우지 못함. 따라서 항상 기존의 값을 삭제 해주어야 함

    Keychain Item

    • Keychain에 데이터를 저장할 때, 데이터를 Wrapping해서 Item형태로 저장
    • Data & Attribute 형태 → Item
    • Attribute를 통해 데이터를 검색 및 접근

    Keychain 속성(kSecClass)

    💥 kSecClass : 저장된 Item이 어떤 정보를 가지고 있는지 나타낸다.

    💥 각 암호화 데이터 별로 다른 Attribute를 가진다.

    💥 k- 접두어는 [konstant = constant] 이므로 붙는다.

    • kSecClassGenericPassword : 일반 암호 항목
    • kSecClassInternetPassword : 인터넷 비밀 항목
    • kSecClassCertificate : 인증서 항목
    • kSecClassIdentity : 아이디 항목
    • kSecClassKey : 암호화 키 항목
    • kSecClass : 데이터 종류 지정
    • kSecAttrAccount : 데이터 저장하기 위한 Key 입력 [개인키]
    • kSecAttrService: 데이터 저장하기 위한 Key 입력 [공개키]
    • kSecValueData : 저장할 데이터를 Data Type으로 캐스팅, 실제 저장할 값
    • kSecReturnAttributes : 값이 항목 속성을 반환하는 것에 대한 여부
    • kSecReturnData : 값이 항목 데이터를 반환하는 것에 대한 여부
    • kSecValueData : update할 value

    〰️ 등 등 더 많은 속성은 공식 문서를 통해 확인할 수 있습니다 〰️

    Keychain 사용

    SecItemAdd

    • SecItemAdd 메소드를 호출하고 Keychain에 Dictionary를 추가
    • 결과 값으로 Bool Type 반환됨
    • 중복된 Key 값으로 추가될 경우에는 이 전 값을 삭제하던가 updateItem해서 업데이트 해준다.
    func addItem(id: Any, password: Any) -> Bool {
        // Keychain Query 정의
        let addQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                   kSecAttrAccount: id,
                                     kSecValueData: (password as AnyObject).data(using: String.Encoding.utf8.rawValue) as Any]
        
        let result: Bool = {
          let status = SecItemAdd(addQuery as CFDictionary, nil)
          if status == errSecSuccess {
            return true
          } else if status == errSecDuplicateItem {
            return updateItem(value: pwd, key: id)
          }
          
          print("addItem Error : \(status.description))")
          return false
        }()
        
        return result
      }


    SecItemCopyMatching

    • SecItemCopyMatching 메소드 호출하고 Keychain에 Key값을 조회
    • 결과 값으로는 원하는 형태로 Data를 캐스팅하여 반환
    func readItems(key: Any) -> String? {
        let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                kSecAttrAccount: key,
                           kSecReturnAttributes: true,
                                 kSecReturnData: true]
        var item: CFTypeRef?
        let result = SecItemCopyMatching(query as CFDictionary, &item)
        
        if result == errSecSuccess {
          if let existingItem = item as? [String: Any],
             let data = existingItem[kSecValueData as String] as? Data,
             let password = String(data: data, encoding: .utf8) {
            return password
          }
        }
        
        print("readItems Error : \(result.description)")
        return nil
      }


    SecItemUpdate

    • SecItemUpdate 메소드 호출하고 update된 결과 값을 반환
    • update할 상황은 Key값이 동일한 상태에서 Value를 최신화 시켜주는 것
    • 따라서, 직전의 Keychain Query 정보를 Dictionary 형태로 담아주고 Update할 Query Dictionary와 함께 SecItemUpdate에 전달
    func updateItem(value: Any, key: Any) -> Bool {
        let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                        kSecAttrAccount: key]
        let updateQuery: [CFString: Any] = [kSecValueData: (value as AnyObject).data(using: String.Encoding.utf8.rawValue) as Any]
        
        let result: Bool = {
          let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
          if status == errSecSuccess { return true }
          
          print("updateItem Error : \(status.description)")
          return false
        }()
        
        return result
      }


    SecItemDelete

    • Keychain 삭제를 위한 Query를 작성하고 SecItemDelete 메소드 호출
    • 매칭되는 Key 값의 Keychain을 Update하여 결과 값을 반환
    func deleteItem(key: String) -> Bool {
        let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                      kSecAttrAccount: key]
        let status = SecItemDelete(deleteQuery as CFDictionary)
        if status == errSecSuccess { return true }
        
        print("deleteItem Error : \(status.description)")
        return false
      }


    ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        
        if KeyChain.shared.addItem(id: "honggildong", pwd: "141433") {
          valueLabel.text = "addItem Success!!!"
          print("addItem Success!!!")
        }
      }
    
    @IBAction func tapOnButton(_ sender: Any) {
        
        if KeyChain.shared.deleteItem(key: "honggildong") {
          valueLabel.text = "삭제 성공"
        }
      }
    viewDidLoad()에서 addItem하고 성공하면 label Text 변경

    버튼 클릭하면 deleteItem을 하여 키체인 정보 삭제 후 label Text 변경

    Keychain Error Codes

    Error CodeError ValueError Description
    errSecSuccess0No error encountered 오류가 발생하지 않았습니다.
    errSecUnimplemented-4Function or operation not implemented 구현되지 않은 기능 및 작업이 있습니다.
    errSecParam-50One or more parameters passed to the function were not valid 함수에 전달된 하나 이상의 매개변수가 유효하지 않습니다.
    errSecAllocate-108Failed to allocate memory 메모리 할당에 실패 하였습니다.
    errSecNotAvailable-25291No trust results are available 신뢰할 수 있는 결과가 없습니다.
    errSecAuthFailed-25293Authorization / authentication Failed 승인 / 인증 실패 하였습니다.
    errSecDuplicateItem-25299The item already exists 항목이 이미 존재합니다.
    errSecItemNotFound-25300The item cannot be found 항목을 찾을 수 없습니다.
    errSecInteractionNotAllowed-25308Interaction with the Security Server is no allowed 보안 서버와의 상호 작용이 허용되지 않습니다.
    errSecDecode-26275Unable to decode the provided data 제공된 데이터를 디코딩할 수 없습니다.

    Keychain OpenSource

    💾 KeychainSwift

    • 위와 같이 Apple에서 기본적으로 제공해주는 메소드들만으로도 Keychain을 구성할 수 있다.
    • 하지만, 더욱 간편하게 사용할 수 있는 오픈소스가 있다.
    • Podfile에 KeychainSwift를 install 해준다.
    • 프로젝트를 열고 KeychainSwift를 import & 인스턴스 생성을 한다.
    • 인스턴스를 통해 get, set, delete, clear 등의 메소드를 사용한다.
    • 자세한 내용은 Github Page를 통해 안내하겠습니다.

    번외 및 마무리

    • Keychain라고 해서 무조건 앱을 삭제해도 데이터가 유지되는 것은 아니다.
    • iOS 10.3.3 이상 기기에서 iTunes & iCloud 백업 / 복원을 했을 때, 사라지는 현상이 발생한다고 한다.
    • iTunes 백업 / 복원 상황에서는 kSecAttrAccessible 설정을 해주면 사라지지 않을 수 있다.
    • 다만, iCloud 백업 / 복원 상황에서 사용자가 iCloud 설정 > Keychain 백업 옵션을 OFF 해놓았다면, 삭제될 수 있다.
    • 확실한 암호화 & 복호화 방법을 사용하는 솔루션을 사용하거나, 서버를 이용하는 것이 보다 안전할 것이라고 생각한다.
    • 위에 작성한 예시들 뿐만 아니라, 다른 Class Type들은 어떻게 사용할 수 있을지에 대한 고민도 필요하다고 생각한다.


    References


    Uploaded by N2T

Designed by Tistory.