YunDaeHyeon
← Lab

KeyChain에 대해

스터디2025.08.09
  • KeyChain
  • Keychain

Keychain Service에 대해

Keychain services | Apple Developer Documentation

이미지 출처 : https://developer.apple.com/documentation/security/keychain-services

KeyChain은 디바이스 내부에 위치한 암호회된 데이터베이스 입니다.

애플리케이션 내부에 데이터를 직렬화하여 저장하는 UserDefaults와 달리 KeyChain은 디바이스 내부에 데이터를 저장하므로 앱을 삭제해도 남아있습니다.

이러한 특성 때문에 인증 정보 등 지속적으로 관리를 해야하는 민감한 데이터들을 저장하기에 용이합니다.

KeyChain은 일반적인 저장 방식과 달리 위 첨부된 사진과 같이 비밀번호에 국한되지 않습니다.

내부적으로 OS에서 직접 관리하며 하드웨어 레벨 암호화를 지원하기 때문에 다양한 정보를 저장할 수 있습니다.

KeyChain에 저장하는 대표적인 민감한 정보들을 예시로 들어오면 다음과 같습니다.

  • 로그인 비밀번호
  • 인증 토큰
  • 결제 데이터
  • 신용카드 정보
  • 알고리즘 암호키
  • 인증서

KeyChain API

KeyChain을 이용하기 위한 API는 Security라는 퍼스트 파티에 정의되어 있습니다.

1. Keychain Item

이미지 출처 : https://developer.apple.com/documentation/security/keychain-items

공식문서에서는 KeyChain에 특정 기밀 정보(Data)를 저장할 때 이를 암호화(Encryption)시켜 Item의 형태로 저장한다고 합니다.

또한 Data와 Attribute를 함께 포장해서 1개의 Item으로 만듭니다.

이 Attribute를 통해 Keychain에 저장된 Data를 검색하여 접근할 수 있습니다.

Keychain = Data(Encryption) + Attributes)

이를 통해 Keychain Service는 Data를 암호화하고 Attribute와 함께 KeyChain에 Item을 저장하는 것을 관리해주는 역할을 수행함을 알 수 있습니다.

추후 승인된 프로세스 혹은 접근 권한이 부여된 프로세스(authorized processes) 를 통해 KeyChain Service를 사용하여 Attribute를 통해 Item을 찾고 Data의 암호를 해제할 수 있습니다.

As shown in Figure 1, keychain services handles data encryption and storage (including data attributes) in a keychain, which is an encrypted database stored on disk. Later, authorized processes use keychain services to find the item and decrypt its data.

2. Keychains

Keychains | Apple Developer Documentation

위 문서를 보면 iOS와 MacOS 환경에서의 KeyChain이 다르다는 것을 알 수 있습니다.

🤔
하지만 문서 하단에 있는 Topics에 명시된 API 대다수가 Deprecated 되어있어.. iOS와 MacOS의 KeyChain 관리 방식을 통일시키려고 하는 것처럼 느껴졌습니다. 그래도 중요한 API는 남아있네요..

  • iOS의 KeyChain
  • iOS의 여러 앱들은 single Keychain에 접근할 수 있습니다.
  • 이때 single Keychain은 앱마다 개별적인 KeyChain을 가지는 것이 아닌, N개의 앱들이 같은 KeyChain을 공유한다..라는 의미인 것 같습니다.
  • 아이폰의 잠금(Lock), 잠금 해제(UnLock) 상태에 따라 KeyChain도 자동으로 동일하게 잠금/잠금 해제 상태가 됩니다.
  • ⭐ 여러 앱이 동일한 KeyChain을 공유하고 있지만 특정한 1개의 앱이 KeyChain에 들어있는 모든 Item들에 접근할 수 있는 것은 아닙니다.

→ 즉, 특정 앱은 자신이 가진 KeyChain Item에만 접근할 수 있으며 만약 소속된 Group이 있는 경우 해당 Group의 Item에는 접근할 수 있다고 적혀있네요… 👀

  • macOS의 KeyChain
  • macOS는 여러 개의 KeyChain을 가질 수 있습니다.
  • 특히 맥북에 있는 Keychain Access 앱을 통해 사용자가 직접 KeyChain을 관리할 수 있고 iOS와 마찬가지로 기본 키체인으로 작업할 수 있습니다.

macOS에서 직접 KeyChain Access를 통해 키체인 내부를 확인하는 모습

3. Access Control Lists

Access Control Lists | Apple Developer Documentation

위 내용은 macOS에서 KeyChain Item에 접근 가능한 앱을 제어하는 방법에 대해 서술되어 있습니다.

하지만 이는 iOS와 iCloud 기반 KeyChain을 사용하는 macOS에선 사용할 수 없다고 적혀있어 그냥 넘어갔습니다. 👀

4. KeyChain을 사용한 기밀 정보 관리법

Using the keychain to manage user secrets | Apple Developer Documentation

위 문서는 사용자의 경험을 중심으로 앱이 최소한의 개입으로 안전하게 사용자의 민감한 정보를 관리하는 방법에 대해 서술하고 있었습니다.

이미지 출처 : https://developer.apple.com/documentation/security/using-the-keychain-to-manage-user-secrets

위 이미지는 공식문서에서 예시를 둔 인터넷 비밀번호 저장 프로세스 입니다.

위 흐름을 토대로 주요 API에 대해 설명해보겠습니다.

KeyChain에 저장되는 민감한 정보(Data)를 사용자의 비밀번호라고 가정해보겠습니다.

4-1. 처음 로그인 시도 시 SecItemAdd(_:_)

  • 앱 실행 후 KeyChain에 자격 증명이 없을 경우 사용자에게 직접 입력을 요청합니다. (prompt user)
  • 사용자가 입력한 후 인증에 성공했을 시 SecItemAdd(_:_)을 통해 KeyChain에 값을 저장합니다.
  • 이후 재로그인없이 Keychain에서 비밀번호를 자동으로 조회시켜 로그인을 시도합니다.

4-2. 일반적인 재인증 시 SecItemCopyMatching(_:_:)

  • 앱을 재실행하거나 세션 만료 등의 상황이 발생했을 때 활용됩니다.
  • 앱이 SecItemCopyMatching 를 사용하여 Keychain을 검색합니다.
  • 만약 KeyChain 내부에 사용자의 비밀번호가 존재한다면 바로 인증을 진행합니다.
  • 이는 사용자 개입 없이 자연스럽게 로그인이 유지될 수 있도록 해줍니다.

4-3. 비밀번호가 변경되거나 갱신되었을 때 SecItemUpdate(_:_:)

  • KeyChain은 외부에서 수정될 수 있습니다.
  • 사용자가 웹이나 외부 환경에서 비밀번호를 변경했다면 KeyChain에 저장되어있는 Data는 자연스럽게 레거시가 되어 인증이 실패하는 상황이 발생합니다.
  • 만약 인증이 실패하는 상황이 발생한다면 앱은 사용자 입력을 다시 요청합니다.

→ 이때 SecItemUpdate(_:_:) 을 통하여 기존 Keychain 값을 갱신합니다.

4-4. 로그아웃 또는 연결 해제 시 SecItemDelete(_:)

  • 사용자가 특정 계정을 완전히 삭제하거나 민감한 정보를 제거하고 싶을 경우 Keychain에 존재하는 데이터를 제거해야합니다.
  • 이를 위해 SecItemDelete(_:) 을 호출하여 Keychain 내부의 자격 증명을 완전히 삭제할 수 있습니다.

위를 통하여 사용자가 직접적으로 개입해야하는 부분에 대해 표로 정리하면 아래와 같습니다.

상황사용자 개입주요 함수동작
최초 로그인필요SecItemAdd새로운 자격 증명 저장
자동 로그인불필요SecItemCopyMatching기존 정보 조회
비밀번호 변경필요SecItemUpdate기존 정보 갱신
로그아웃불필요SecItemDelete정보 삭제

직접 만들어보기

Adding a password to the keychain | Apple Developer Documentation

이제 위 공식문서를 토대로 직접 keychain을 사용해보겠습니다.

우선 앱에서 사용할 자격 증명 정보를 저장할 구조체를 정의합니다.

struct Credentials {
    var username: String
    var password: String
}

그 후 Keychain 접근 결과를 나타내기 위한 Error 열거형을 정의합니다.

enum KeychainError: Error {
    case noPassword
    case unexpectedPasswordData
    case unhandledError(status: OSStatus)
}

이때 대부분의 API 반환값이 _OSStatus_로 되어있는데.. 아무리 찾아봐도 관련된 내용을 찾을 수 없었습니다..ㅠㅠ
https://developer.apple.com/documentation/security/security-framework-result-codes
이런 링크가 있긴 했지만 이해가 잘 안가네요.. 검색 결과론 _macOS 및 iOS와 같은 Apple 운영체제 레벨에서 특정 함수의 결과를 나타내는 정수 코드_라고 나오는데.. 잘 모르겠습니다.
KeyChain 함수를 호출 후 반환된
OSStatus 값을 확인하여 _noErr(성공, 값은 0)__이라면 그냥 다음 단계로, 특정 정수가 출력되었다면 어떠한 오류가 발생했다는 걸로 인지했습니다.

이와 관련된 OSStatus의 값을 찾을 수 있는 사이트는 아래와 같습니다._
https://www.osstatus.com/

마지막으로 앱의 서버를 입력합니다. (재인증을 시도할 서버를 말하는 것 같습니다.)

let server = "www.example.com"

이제 정의한 내용을 토대로 KeyChain에 저장하기 위핸 쿼리를 작성해보면 다음과 같습니다.

let credentials = Credentials(username: "daehyeon", password: "1234")
let account = credentials.username
let password = credentials.password.data(using: String.Encoding.utf8)!
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
                            kSecAttrAccount as String: account,
                            kSecAttrServer as String: server,
                            kSecValueData as String: password]

이때 kSecClass as String: kSecClassInternetPassword 가 존재하는데 kSecClass의 정의는 아래와 같습니다.

KSecClass 는 KeyChain Services에서 사용되는 딕셔너리의 키를 의미합니다.

해당 키는 KeyChain Item의 클래스를 나타내며 어떤 종류의 Item을 생성할 지, 검색할 지 지정하는 데 사용됩니다.

KeyChain은 단순 비밀번호가 아닌 신용카드 번호, 인증서 등 다양한 데이터의 저장을 지원하는데 이 kSecClass를 통해 어떤 유형의 데이터를 저장할 지, 검색할 지를 결정할 수 있습니다.

이 Key에 따라 저장할 수 있는 데이터의 종류도 달라집니다.

해당 정리는 아래 블로그에 정리가 매우 잘 되어있어서 링크로 남기겠습니다.

kSecClass & Item Class Value in Keychain - 키체인 — IT Document

  • kSecAttrAccount as String: account
  • kSecAttrServer as String: server

→ 이 2가지 데이터는 Attribute 정보를 의미합니다.

  • kSecValueData as String: password
  • 위 데이터가 가장 중요하며 실제 저장될 민감한 정보를 의미합니다.
  • 이때 Data 로 인코딩하여 query에 넣어야하기 때문에 공식 문서에서 credentials.password.data(using: String.Encoding.utf8)! 를 수행한 것으로 보입니다.

그 후 작성한 쿼리를 SecItemAdd에 전달하여 KeyChain에 추가하면 끝입니다.

let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }

이때 SecItemAdd의 정의는 아래와 같이 되어있는데,

func SecItemAdd(
    _ attributes: CFDictionary,
    _ result: UnsafeMutablePointer<CFTypeRef?>?
) -> OSStatus

반환값(OSStatus )를 확인하여 KeyChain에 저장이 성공적으로 수행되었는지 항상 확인하는 것이 좋다고 합니다.


실제 저장한 KeyChain을 검색해서 가져오거나 변경되었을 때, 수정할 때, 삭제할 때 등은 추후에 더 정리하겠습니다.