Keychain в iOS: що це, архітектура та робота з секретами

Автор: IT Sectr Опубліковано: 2026-04-04 Час читання: 9 хв

Keychain — захищене сховище в iOS, призначене для безпечного зберігання паролів, криптографічних ключів, сертифікатів і конфіденційних нотаток. За даними Apple Security Documentation (2025), Keychain використовує апаратне шифрування через Secure Enclave на всіх пристроях з чипом A7 і новіших. Розуміння архітектури iOS Keychain необхідне кожному розробнику для правильного зберігання токенів і секретів додатку.

Головне

  • iOS Keychain — зашифрована база даних SQLite для зберігання секретів з апаратним захистом через Secure Enclave.
  • Protection Class визначає, коли дані доступні: при розблокованому пристрої, після першого розблокування або завжди.
  • Access Control List (ACL) — механізм обмеження доступу до елементів Keychain, включаючи біометричну аутентифікацію.
  • SecItemAdd і SecItemCopyMatching — основні API Security framework для запису та читання елементів.
  • kSecAttrSynchronizable — прапорець, що дозволяє синхронізацію Keychain через iCloud для доступу на всіх пристроях користувача.

Що таке Keychain в iOS?

iOS Keychain — захищений механізм зберігання конфіденційних даних, вбудований в операційну систему Apple. На відміну від UserDefaults або звичайних файлів, Keychain шифрує всі елементи на апаратному рівні та забезпечує тонкий контроль доступу на основі політик безпеки.

Keychain був представлений в iOS 2.0 і з тих пір зазнав значних змін: в iOS 7 додалася підтримка апаратних ключів через Secure Enclave, в iOS 9 — розділення Keychain між додатками через Access Groups, в iOS 13 — підтримка біометричної прив'язки через LAContext. За даними Apple WWDC Session (2024), понад 90% iOS-додатків у топ-100 App Store використовують Keychain для зберігання токенів аутентифікації.

Архітектурно Keychain — це зашифрована SQLite-база даних, розташована поза пісочницею додатку. Кожен елемент (SecItem) шифрується окремим ключем, який, у свою чергу, захищений апаратним ключем Secure Enclave. Системна служба Securityd керує доступом до Keychain на основі прав додатку (entitlements) і запитаного класу захисту.

Важлива перевага Keychain перед іншими способами зберігання: дані автоматично шифруються та дешифруються ОС. Розробнику не потрібно реалізовувати криптографію вручну — достатньо викликати SecItemAdd з правильними параметрами. iOS гарантує, що дані з Keychain не можуть прочитати інші додатки (при правильному налаштуванні Access Groups).

Архітектура Keychain

Архітектура Keychain включає кілька рівнів: фізичний (Secure Enclave), системний (Security.framework), прикладний (API SecItem*) та логічний (Access Groups, Protection Classes). Розуміння кожного рівня допомагає правильно проектувати зберігання секретів.

SecItemAdd і SecItemCopyMatching

Основний API для роботи з Keychain — функції Security framework: SecItemAdd для додавання, SecItemCopyMatching для читання, SecItemUpdate для оновлення та SecItemDelete для видалення. Кожна функція приймає словник query, який описує атрибути шуканого або збереженого елемента.

Ключові атрибути query: kSecClass — тип елемента (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount — унікальний ідентифікатор у рамках класу, kSecValueData — дані, що зберігаються (Data), kSecAttrAccessible — клас захисту. SecItemCopyMatching з прапорцем kSecReturnData повертає дані елемента, з kSecMatchLimit — кількість результатів.

Важливо: всі функції повертають статус OSStatus. Успішна операція повертає errSecSuccess (0). Помилки: errSecItemNotFound (-25300) — елемент не знайдено, errSecDuplicateItem (-25299) — елемент вже існує, errSecAuthFailed (-25293) — біометрична аутентифікація не пройдена. Розробник повинен обробляти кожен статус коректно.

Класи захисту (Protection Class)

Protection Class — атрибут kSecAttrAccessible, який визначає, коли дані в Keychain доступні для читання. iOS підтримує шість класів захисту з різним рівнем доступності та безпеки.

Рекомендований клас для більшості сценаріїв — kSecAttrAccessibleWhenUnlockedThisDeviceOnly: дані доступні лише при розблокованому пристрої та не копіюються в iCloud Backup. Для даних, які повинні бути доступні після перезавантаження (але лише після першого розблокування), використовуйте kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Для критичних даних, що вимагають біометричної аутентифікації при кожному доступі, комбінуйте kSecAttrAccessibleWhenUnlockedThisDeviceOnly з ACL, що вимагає біометрію.

Класи без суфікса ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) дозволяють копіювання в iCloud Backup. Це зручно для користувача, але знижує безпеку — дані можуть бути відновлені з бекапу. Для токенів аутентифікації завжди використовуйте ThisDeviceOnly.

Access Control Lists (ACL)

Access Control List (ACL) — механізм, який обмежує операції з елементом Keychain на основі аутентифікації користувача. ACL задається через SecAccessControlCreateWithFlags і передається в атрибут kSecAttrAccessControl при збереженні елемента.

Підтримувані прапорці: kSecAccessControlUserPresence — будь-яка аутентифікація (Face ID, Touch ID або код-пароль), kSecAccessControlBiometryCurrentSet — лише біометрія (зареєстровані на даний момент відбитки або обличчя), kSecAccessControlDevicePasscode — лише код-пароль. ACL застосовується до кожної операції: читання, оновлення та видалення елемента також вимагають аутентифікації.

На iOS 15+ з'явився прапорець kSecAccessControlWatch — для Apple Watch, який дозволяє аутентифікацію через парні годинники. ACL можна комбінувати: наприклад, kSecAccessControlUserPresence або kSecAccessControlBiometryAny з опціональним код-паролем (орієнтація .or).

Типи даних в Keychain

iOS Keychain підтримує чотири основних класи елементів (kSecClass), кожен призначений для свого типу даних. Правильний вибір класу спрощує організацію та пошук елементів.

kSecClassGenericPassword — загальний пароль: найбільш часто використовуваний клас. Зберігає довільні бінарні дані (Data) з унікальним ключем (kSecAttrAccount). Підходить для токенів, ключів API, PIN-кодів. Не вимагає додаткових entitlements для використання.

kSecClassInternetPassword — інтернет-пароль: зберігає дані, пов'язані з мережевим ресурсом. Додаткові атрибути: kSecAttrServer (домен сервера), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS може автоматично заповнювати такі паролі через AutoFill.

kSecClassKey — криптографічний ключ: для зберігання ключів шифрування (AES, RSA, EC). Ключ зберігається як SecKeyRef, а не як Data. kSecClassCertificate — сертифікат X.509 для зберігання та перевірки цифрових сертифікатів. Обидва класи вимагають розуміння криптографічних операцій і правильного налаштування атрибутів.

На практиці 95% випадків використання Keychain в мобільних додатках покриваються kSecClassGenericPassword для зберігання токенів аутентифікації та kSecClassKey для зберігання приватних ключів шифрування. kSecClassCertificate використовується рідко — зазвичай у корпоративних додатках з власним PKI.

Приклади коду: робота з Keychain на Swift

Розглянемо практичні приклади роботи з Keychain на Swift з використанням Security framework. Кожен приклад включає обробку помилок і правильну конфігурацію Protection Class.

Збереження та читання токена

Базовий приклад зберігає токен аутентифікації в Keychain з захистом WhenUnlockedThisDeviceOnly. Ключ (kSecAttrAccount) — ідентифікатор сервісу, дані (kSecValueData) — токен у форматі Data.

swift
import Security

enum KeychainError: Error {
    case unexpectedStatus(OSStatus)
}

func saveToken(token: String, service: String) throws {
    let data = Data(token.utf8)
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: "auth_token",
        kSecValueData as String: data,
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)
    let status = SecItemAdd(query as CFDictionary, nil)
    guard status == errSecSuccess else {
        throw KeychainError.unexpectedStatus(status)
    }
}

func readToken(service: String) throws -> String {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: "auth_token",
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result
    )
    guard status == errSecSuccess,
        let data = result as? Data else {
        throw KeychainError.unexpectedStatus(status)
    }
    return String(decoding: data, as: UTF8.self)
}

Збереження з біометричною прив'язкою

Приклад демонструє використання SecAccessControlCreateWithFlags для прив'язки ключа до біометрії. Кожен доступ до елемента вимагатиме Face ID або Touch ID.

swift
import LocalAuthentication

func saveWithBiometry(data: Data, key: String) throws {
    let accessControl = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        .biometryCurrentSet,
        nil
    )

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessControl as String: accessControl as Any
    ]

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

Keychain Sharing між додатками

Приклад показує налаштування Access Group для спільного доступу до Keychain між додатками одного розробника. Потрібен entitlement keychain-access-groups.

swift
// Capabilities: Keychain Sharing увімкнено
// App IDs: group.com.example.shared

func saveSharedToken(token: Data) {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "shared_token",
        kSecValueData as String: token,
        kSecAttrAccessGroup as String:
            "group.com.example.shared",
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemAdd(query as CFDictionary, nil)
}

Найкращі практики роботи з Keychain

Правильне використання iOS Keychain вимагає дотримання кількох ключових правил, які запобігають типовим вразливостям і втраті даних.

Використовуйте ThisDeviceOnly для всіх секретів аутентифікації: kSecAttrAccessibleWhenUnlockedThisDeviceOnly гарантує, що токени не потраплять в iCloud Backup. Якщо зловмисник отримає доступ до бекапу, дані Keychain з цим прапорцем не будуть доступні. Виняток — дані, які повинні бути доступні на всіх пристроях користувача (наприклад, ключі шифрування для власних сервісів), для яких використовуйте kSecAttrAccessibleWhenUnlocked з kSecAttrSynchronizable.

Не зберігайте сирі паролі — зберігайте хеші або токени сесії. Apple Security Guide (2025) рекомендує ніколи не зберігати пароль користувача в Keychain у відкритому вигляді. Замість цього зберігайте refresh token, отриманий від сервера після успішної аутентифікації через OAuth 2.0. Пароль використовується лише для отримання токена і негайно видаляється з пам'яті.

Обробляйте помилки Keychain коректно: кожна операція з Keychain повертає OSStatus, який потрібно перевіряти. Особлива увага — errSecItemNotFound (токен закінчився або видалений) і errSecAuthFailed (біометрія не пройдена). У першому випадку додаток повинен запитати нову аутентифікацію, у другому — показати користувачеві альтернативний спосіб (код-пароль). Ніколи не ігноруйте статус errSecItemNotFound — це призведе до падіння додатку при спробі читання nil.

Тестуйте Keychain на реальному пристрої: симулятор не має Secure Enclave і не підтримує біометричні ACL. Завжди перевіряйте сценарії: перший запуск, відновлення з бекапу, зміна пароля пристрою, видалення та перевстановлення додатку. На реальному пристрої Keychain зберігається при видаленні додатку, але лише якщо не використовувався прапорець kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, який очищається при знятті код-пароля.

Мінімізуйте кількість операцій з Keychain: кожна операція читання або запису — це звернення до системної служби Securityd, яка може блокувати потік. Кешуйте прочитані токени в пам'яті на час сесії та звертайтеся до Keychain повторно лише при перезапуску додатку або помилці аутентифікації (401 від сервера). iOS автоматично блокує Keychain при блокуванні пристрою, тому плануйте читання через LAContext із запитом біометрії.

Часті запитання

Чи можна зберегти дані в Keychain і прочитати їх після перезавантаження?

Так, використовуйте клас захисту kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly або kSecAttrAccessibleAfterFirstUnlock. Дані будуть доступні після першого розблокування пристрою після перезавантаження. Для автоматичного доступу при запуску додатку (без очікування розблокування) використовуйте kSecAttrAccessibleAlways, але це знижує безпеку.

Як очистити Keychain при виході користувача?

Викличте SecItemDelete з query, що містить kSecClass для кожного типу даних. Для повного очищення всіх елементів додатку виконайте: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Багаторазово викличте для kSecClassKey, kSecClassCertificate та kSecClassInternetPassword.

У чому різниця між kSecAttrAccessible та kSecAttrAccessControl?

kSecAttrAccessible визначає, коли дані доступні (при розблокуванні, після першого розблокування тощо). kSecAttrAccessControl визначає, хто може отримати доступ (біометрія, код-пароль, будь-яка аутентифікація). Вони комбінуються: спочатку Protection Class, потім ACL. Наприклад, дані доступні лише при розблокуванні І лише після Face ID.

Чому SecItemCopyMatching повертає errSecItemNotFound?

Причини: елемент ніколи не зберігався, елемент був видалений при знятті код-пароля (якщо використовувався kSecAttrAccessibleWhenPasscodeSet), додаток був перевстановлений (Keychain зберігається, але не відновлюється з бекапу на новому пристрої), змінився Access Group або ідентифікатор команди розробника. Перевірте kSecAttrService та kSecAttrAccount.

Як перевірити, чи підтримує пристрій біометрію в Keychain?

Використовуйте LAContext з LocalAuthentication: викличте context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Якщо повертає true — пристрій підтримує Touch ID або Face ID. Для Keychain ACL використовуйте прапорець biometryCurrentSet (лише поточні біометричні дані) або biometryAny (будь-які зареєстровані раніше).

Підсумки

  • iOS Keychain — апаратно захищене сховище для секретів з шифруванням через Secure Enclave та контролем доступу через ACL.
  • Security framework надає функції SecItemAdd, SecItemCopyMatching, SecItemUpdate та SecItemDelete для роботи з елементами.
  • Protection Class (kSecAttrAccessible) вибирається на основі сценарію: WhenUnlockedThisDeviceOnly — стандарт для токенів.
  • ACL з біометрією (kSecAttrAccessControl) додає вимогу Face ID або Touch ID для кожної операції читання.
  • kSecClassGenericPassword покриває 95% випадків — зберігання токенів, ключів API, PIN-кодів та нотаток.
  • ThisDeviceOnly запобігає копіюванню секретів в iCloud Backup — обов'язково для токенів аутентифікації.
  • Правильна обробка OSStatus та тестування на реальному пристрої — обов'язкові практики для надійної роботи з Keychain.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також