KeyChain Item(KeyChain Service)

IOS에서 작은 데이터 쪼가리를 암호화해 저장하는 기능입니다. 사파리의 아이디/비밀번호 저장기능이 그 예라고 할 수 있습니다. 단순히 아이디 비밀번호만 국한되는것이 아닌 신용카드 정보, 메모 등등을 암호화해 안전하게 저장해줄 수 있습니다. 또 KeyChain은 앱에 종속되는게 아닌 기기 자체에 종속이 됩니다. 따라서 앱이 삭제 된다 하더라도 저장된 값들은 그대로 남아있습니다.

IOS와 MacOS의 Keychain은 keychain의 개수에서 차이가 있습니다. IOS의 KeyChain은 기기의 잠금 상태에 따라 KeyChain이 잠기고 열립니다. 그러나 MacOS는 여러개의 KeyChain을 지원합니다. 그래서 KeyChain 문서에 들어가시면 여러개의 키체인을 사용/관리할 수 있는 MacOS에서만 사용할 수 있는 Topic이 있는것을 볼 수 있습니다.더 상세한 설명은 애플 개발자 문서에서 확인하시면 될 듯 합니다. 이번시간에는 IOS에서도 사용할 수 있는 KeyChain Item에 대해서 알아 봅시다.

 

KeyChain Item

데이터와 함께 공개적으로 표시가 되는 속성(Attribute)들을 하나의 패키지로 만든 집합을 의미합니다. Attribute는 Item을 제어하고 검색을 할 수 있도록 하는 역할을 해줍니다. 즉 저장할 데이터를 암호화 시켜주고, 그 암호를 찾을수 있는 Attribute들을 하나로 묶어서 저장하는것을 KeyChain Item이라고 합니다. 간단한 프로젝트로 해당 기능을 구현해보겠습니다.

 

간단하게 아이디/비밀번호를 입력하고 회원가입 버튼을 누르면 KeyChain에 저장되게 하고, 저장된 아이디/비밀번호를 입력하고 로그인 버튼을 누르면 빨간색 뷰로 이동을 하는 프로젝트를 구현해보겠습니다.

struct UserInfo{
    var id : String
    var password : String
}

//키 체인 에러
enum KeychainError : Error{
    case noPassword
    case unexpectedPasswordData
    case unhandledError(status: OSStatus)
}

KeyChain에서 query를 이용해 자신이 원하는 대로 Attribute를 추가 할 수 있습니다. query문은 암호의 유형, Attribute, 저장데이터의 형식 등등을 지정해 줄 수 있습니다. 

  • kSecClass : 암호화 할 데이터의 항목을 지정하는 키
  • kSecClassGenericPassword : 일반 암호를 나타냅니다.
  • kSecAttrAccount : 계정 Attribute입니다.
  • kSecAttrService : 저장된 암호의 앱의 번들 id를 저장하는 Attribute
  • kSecValueData : 저장할 암호
//암호 저장
	let userInfo = UserInfo(id: idTextField.text!, password: passwordTextField.text!)
        let id = userInfo.id
        let password = userInfo.password.data(using: String.Encoding.utf8)
        
        let query : [String : Any] = [
        	kSecClass as String: kSecClassGenericPassword,
        	kSecAttrAccount as String: id,
        	kSecValueData as String: password,
        	kSecAttrService as String: "example.service"
        ]
        
        let status = SecItemAdd(query as CFDictionary, nil)
        if status == errSecSuccess{
            print("add success")
        }else if status == errSecDuplicateItem{
            //비밀번호가 이미 존재하는 경우
            //상황에 맞춰서 구현
            print("alreay password")
        }else{
            print(KeychainError.unhandledError(status: status))
        }

attribute 값인 id로 저장된 암호를 조회, 그리고 현재 입력된 암호와 비교합니다. 조회 하는것 역시도 query를 이용합니다. kSecReturnAttributes는 암호가 저장된 Item의 Attribute들을 리턴 유무를 나타냅니다. kSecReturnData는 저장된 암호의 리턴 유무를 나타냅니다. 

//암호 조회
	let account = idTextField.text!
        let query: [CFString: Any] = [
        	kSecClass : kSecClassGenericPassword,
            kSecAttrAccount : account,
            kSecReturnAttributes : true,
            kSecReturnData : true
        ]
        var item : CFTypeRef?
        if SecItemCopyMatching(query as CFDictionary, &item) != errSecSuccess{
            return
        }
        guard let existingItem = item as? [String: Any] else { return }
        guard let data = existingItem[kSecValueData as String] as? Data else { return }
        guard let password = String(data: data, encoding: .utf8) else { return }
        if password == pwTextField.text!{
        guard let diaryViewController = diaryViewController else { return }
        diaryViewController.modalPresentationStyle = .fullScreen
        present(diaryViewController, animated: true)
        }

실행영상

참고 사이트 : https://developer.apple.com/documentation/security/keychain_services