Keychain (Связка ключей) — защищённое хранилище в iOS, предназначенное для безопасного хранения паролей, криптографических ключей, сертификатов и конфиденциальных заметок. По данным Apple Security Documentation (2025), Keychain использует аппаратное шифрование через Secure Enclave на всех устройствах с чипом A7 и новее. Понимание архитектуры iOS Keychain необходимо каждому разработчику для правильного хранения токенов и секретов приложения.
Главное
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 включает несколько уровней: физический (Secure Enclave), системный (Security.framework), прикладной (API SecItem*) и логический (Access Groups, Protection Classes). Понимание каждого уровня помогает правильно проектировать хранение секретов.
Основной 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 — атрибут kSecAttrAccessible, который определяет, когда данные в Keychain доступны для чтения. iOS поддерживает шесть классов защиты с разным уровнем доступности и безопасности.
Рекомендуемый класс для большинства сценариев — kSecAttrAccessibleWhenUnlockedThisDeviceOnly: данные доступны только при разблокированном устройстве и не копируются в iCloud Backup. Для данных, которые должны быть доступны после перезагрузки (но только после первого разблокирования), используйте kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Для критических данных, требующих биометрической аутентификации при каждом доступе, комбинируйте kSecAttrAccessibleWhenUnlockedThisDeviceOnly с ACL, требующим biometry.
Классы без суффикса ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) разрешают копирование в iCloud Backup. Это удобно для пользователя, но снижает безопасность — данные могут быть восстановлены из бэкапа. Для токенов аутентификации всегда используйте ThisDeviceOnly.
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).
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 с использованием Security framework. Каждый пример включает обработку ошибок и правильную конфигурацию Protection Class.
Базовый пример сохраняет токен аутентификации в Keychain с защитой WhenUnlockedThisDeviceOnly. Ключ (kSecAttrAccount) — идентификатор сервиса, данные (kSecValueData) — токен в формате Data.
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.
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)
}
}
Пример показывает настройку Access Group для общего доступа к Keychain между приложениями одного разработчика. Требуется entitlement keychain-access-groups.
// 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)
}
Правильное использование 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 с запросом биометрии.
Часто задаваемые вопросы
Да, используйте класс защиты kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly или kSecAttrAccessibleAfterFirstUnlock. Данные будут доступны после первой разблокировки устройства после перезагрузки. Для автоматического доступа при запуске приложения (без ожидания разблокировки) используйте kSecAttrAccessibleAlways, но это снижает безопасность.
Вызовите SecItemDelete с query, содержащим kSecClass для каждого типа данных. Для полной очистки всех элементов приложения выполните: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Многократно вызовите для kSecClassKey, kSecClassCertificate и kSecClassInternetPassword.
kSecAttrAccessible определяет, когда данные доступны (при разблокировке, после первой разблокировки и т. д.). kSecAttrAccessControl определяет, кто может получить доступ (биометрия, код-пароль, любая аутентификация). Они комбинируются: сначала Protection Class, затем ACL. Например, данные доступны только при разблокировке И только после Face ID.
Причины: элемент никогда не сохранялся, элемент был удалён при снятии код-пароля (если использовался kSecAttrAccessibleWhenPasscodeSet), app был переустановлен (Keychain сохраняется, но не восстанавливается из бэкапа на новом устройстве), изменился Access Group или identifier команды разработчика. Проверьте kSecAttrService и kSecAttrAccount.
Используйте LAContext из LocalAuthentication: вызовите context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Если возвращает true — устройство поддерживает Touch ID или Face ID. Для Keychain ACL используйте флаг biometryCurrentSet (только текущие биометрические данные) или biometryAny (любые зарегистрированные ранее).
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также