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, требующим biometry.

Классы без суффикса 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), app был переустановлен (Keychain сохраняется, но не восстанавливается из бэкапа на новом устройстве), изменился Access Group или identifier команды разработчика. Проверьте 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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