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, що вимагає біометрію.
Класи без суфікса 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), додаток був перевстановлений (Keychain зберігається, але не відновлюється з бекапу на новому пристрої), змінився Access Group або ідентифікатор команди розробника. Перевірте kSecAttrService та kSecAttrAccount.
Використовуйте LAContext з LocalAuthentication: викличте context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Якщо повертає true — пристрій підтримує Touch ID або Face ID. Для Keychain ACL використовуйте прапорець biometryCurrentSet (лише поточні біометричні дані) або biometryAny (будь-які зареєстровані раніше).
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також