iOS Keychain: де застосунок втрачає дані та права
Найчастіший збій — токени й паролі лежать у UserDefaults або в plist усередині пісочниці застосунку.
Пісочниця захищає від інших програм, але не від бекапу в iCloud і не від фізичного доступу до розблокованого пристрою: файл читається штатними засобами, а при увімкненому шифруванні бекапу потрапляє в хмару разом із копією пристрою.
Інженери знаходять таке на етапі аудиту, коли застосунок уже в продакшені й токени живуть у відкритому вигляді місяцями.
Другий типовий провал — неправильний клас доступності запису в iOS Keychain. За замовчуванням елемент читається після першого розблокування й залишається доступним, поки пристрій увімкнено, — цього достатньо, щоб фонові процеси та розширення витягли токен.
Для чутливих даних потрібен клас, прив'язаний до розблокування, а для біометрії — прапорець, що вимагає підтвердження власника при кожному читанні. Без цього «захищене сховище» перетворюється на звичайну базу.
Третій збій — права доступу. Елемент, записаний без явної групи доступу, не видно розширенню або віджету, і розробники обходять це копіюванням токена в спільний контейнер — тобто знову назовні. Зворотна помилка: занадто широка група відкриває дані всім збіркам команди, включно з тестовими.
Розбираємо, як це закривається: класи доступності під кожен тип секрету, коректна група, міграція наявних записів і перевірка, що після перевстановлення та відновлення з бекапу токени не спливають у відкритому вигляді.
Що дає коректне налаштування сховища
Коректне налаштування сховища — це не «галочка безпеки», а керована поверхня атаки. Коли ми задаємо для кожного секрету свій клас доступності та групу доступу, токени й ключі перестають потрапляти в резервні копії, у логи та в спільний доступ між застосунками на пристрої.
Поверхня атаки скорочується не абстрактно, а за конкретними каналами: секрет не витікає через бекап, не читається іншим застосунком, не залишається на диску після видалення користувачем.
Другий ефект — передбачувана поведінка під час оновлення застосунку. Якщо політика зберігання описана явно, а не «за замовчуванням», то перехід між версіями не ламає доступ до збережених даних: користувач оновлюється і залишається у своїй сесії, а не проходить вхід заново.
Це знімає цілий клас відмов — «дані не читаються після перевстановлення», коли застосунок формально працює, але втрачає все, що мало пережити перевстановлення.
Третій ефект — стабільність релізів. Коли правила доступу до секретів зафіксовані й однакові для всіх збірок, інженери перестають лагодити роз'їжджувану поведінку на різних пристроях і версіях системи.
Помилки доступу до сховища ловляться на етапі перевірки, а не в користувача, і не перетворюються на потік звернень до підтримки.
Перевірити налаштування можна за чек-лістом: для кожного секрету задано клас доступності та момент, коли він потрібен; група доступу обмежує обмін даними лише потрібними застосунками; резервне копіювання виключено там, де секрет не повинен залишати пристрій; поведінка під час оновлення та перевстановлення описана й перевірена; видалення даних відбувається за явним сценарієм. Якщо хоч один пункт не виконано, сховище залишається джерелом ризику, а не захистом.
Режими доступу до сховища: порівняння варіантів
Режим доступності задається атрибутом kSecAttrAccessible під час запису елемента і визначає, за якого стану пристрою дані взагалі читаються. Це не «налаштування на майбутнє»: значення фіксується в момент збереження і змінюється лише перезаписом елемента.
Помилка тут проявляється не одразу — застосунок працює, поки телефон розблокований, і падає на фоновій задачі після перезавантаження або під час блокування екрана.
| Режим | Коли підходить | Обмеження |
|---|---|---|
WhenUnlocked |
Токени доступу, персональні дані, усе, що читається лише в активному інтерфейсі | Недоступно при заблокованому екрані — фонові задачі та push-обробники отримають помилку читання |
AfterFirstUnlock |
Фонова синхронізація, оновлення даних за розкладом, робота після перезавантаження без введення коду | Дані читаються весь час після першого розблокування — вища ціна компрометації пристрою |
WhenUnlockedThisDeviceOnly |
Ключі, прив'язані до конкретного пристрою, чутливі сесії | Не переноситься з резервною копією — при зміні телефону користувач проходить вхід заново |
AfterFirstUnlockThisDeviceOnly |
Фонова робота з прив'язкою до пристрою: синхронізація, аналітика, відкладені відправлення | Поєднує обидва обмеження: доступно після розблокування, але не відновлюється з копії |
WhenPasscodeSetThisDeviceOnly |
Максимально чутливі ключі, де потрібен код-пароль на пристрої | Вимагає встановленого коду-пароля; при його знятті елементи видаляються безповоротно |
Вибір зводиться до двох питань: чи потрібен доступ до даних до розблокування і чи має елемент переживати перенесення на новий пристрій. Якщо фонова синхронізація зобов'язана працювати після перезавантаження — беремо гілку AfterFirstUnlock.
Якщо дані не повинні залишати пристрій — додаємо ThisDeviceOnly. Змішувати не можна: той самий токен, записаний у двох режимах, дає розсинхрон при відновленні з копії.
На практиці режими комбінують у межах одного застосунку: довгоживучий токен сесії — AfterFirstUnlock, бо його читає фонова задача, а ключ для підпису платежу — WhenPasscodeSetThisDeviceOnly. Розбираємо це на етапі проєктування сховища, до першого рядка коду.
Етапи впровадження безпечного сховища
Впровадження сховища починається не з коду, а з інвентаризації того, що застосунок уже записує на диск.
Токени сесій, паролі, ключі підпису — якщо вони лежать у звичайних файлах або налаштуваннях, будь-хто, хто отримав доступ до пристрою або резервної копії, читає їх як відкритий текст.
Тому перший крок — аудит зберігання: пройтися по всіх місцях запису чутливих даних і зафіксувати, що саме і як довго має жити.
- Аудит зберігання. Збираємо повну карту даних: які значення записуються, де, ким читаються і що відбувається при виході з акаунта. На виході — перелік записів із позначкою «переносимо в сховище / видаляємо / залишаємо як є».
- Схема ключів. Проєктуємо структуру записів: окремий ключ на кожен тип даних, єдиний префікс для службових, явні атрибути доступності — коли запис має читатися лише на розблокованому пристрої, а коли переживати перезавантаження.
- Налаштування груп доступу. Якщо в екосистемі кілька застосунків або розширень, зв'язуємо їх спільною групою, щоб обмін токенами йшов без дублювання і без виходу за межі пісочниці.
- Міграція наявних даних. Переносимо накопичені значення зі старого сховища в нове з одноразовим записом при першому запуску, з перевіркою цілісності та відкатом, якщо запис не підтвердився.
- Перевірка сценаріїв. Прогоняємо поведінку при блокуванні екрана, оновленні застосунку, відновленні з резервної копії та зміні пароля пристрою — саме тут випливають помилки доступу.
- Обробка помилок і відмов. Прописуємо, що робить застосунок, якщо запис недоступний: не падає, а коректно просить повторний вхід.
- Передача документації. Віддаємо схему ключів, правила ротації та інструкцію з додавання нових записів — щоб команда не поверталася до розбору заново.
Такий порядок закриває головний ризик: дані перестають бути доступними за межами застосунку, а поведінка при збоях залишається передбачуваною. Опишіть вашу задачу — повернемося з оцінкою та планом робіт.
Що замовник отримує на руки після робіт
Після робіт у вас залишається не «працюючий застосунок», а комплект, за яким інша команда зможе підтримувати сховище секретів без нас.
Усе, що ми спроєктували, фіксується у вигляді конфігурацій, схем і регламентів — їх можна передати внутрішнім інженерам або підряднику на супровід.
- Конфігурації доступу. Набір класів захисту для різних типів даних: паролі та токени сесії, ключі шифрування, службові мітки. Для кожного — свій рівень: коли запис доступний лише при розблокованому пристрої, коли переживає перезавантаження, коли синхронізується між пристроями користувача.
- Схеми зберігання. Карта, що і де лежить: які дані йдуть у захищене сховище, які в загальне, які не зберігаються взагалі. З прив'язкою до життєвого циклу — що видаляється при виході з акаунта, що при зміні пароля, що при відкликанні доступу.
- Політики доступу. Правила, за якими застосунок звертається до секретів: які операції дозволені у фоні, які лише при активному екрані, як обробляється відмова в доступі. Це захищає від витоків через фонові процеси та сторонні бібліотеки.
- Документація. Опис архітектури сховища, точок звернення до нього та типових сценаріїв. Інженер, який уперше відкрив проєкт, розбереться без листування з автором.
- Регламент ротації. Порядок і періодичність зміни ключів і токенів, умови примусового скидання, поведінка при компрометації. Плюс процедура відкату, якщо нова схема зберігання ламає вхід у частини користувачів.
- Чек-ліст перевірок. Що і як тестується перед релізом: збереження після перезапуску, поведінка при блокуванні екрана, коректність міграції зі старої схеми зберігання.
Комплект самодостатній: за ним можна провести аудит, підключити нового розробника або пройти перевірку безпеки без нас. Якщо потрібен розбір вашого поточного сховища — опишіть задачу, повернемося з оцінкою та планом.
Кейс: усунення класу відмов під час оновлення застосунку
Клієнт — сервіс доставки із застосунком на iOS, аудиторія активна, релізи кожні два тижні. Після чергового оновлення почалися скарги: частина користувачів під час відкриття втрачала авторизацію, частина — кошик і адреси.
До підтримки за тиждень надійшло 340 звернень, а частка збоїв читання збережених даних на нових збірках трималася близько 6% від активної бази.
Що було
Дані лежали у зв'язці ключів Keychain без явної політики доступності: запис ішов із налаштуванням за замовчуванням, а читання — з умовою «доступно лише на розблокованому пристрої».
На частині сценаріїв (фонова синхронізація, відновлення після перезапуску) читання повертало порожній результат, застосунок трактував це як «користувач новий» і скидав сесію. Діагностика займала дні: відтворити вдавалося не на кожному пристрої.
Що змінили
Розвели доступ за типами даних: токени — з доступом після першого розблокування та синхронізацією між пристроями користувача, чутливі ключі — строго локально. Додали явну обробку помилок читання: порожня відповідь і помилка доступу тепер різні гілки логіки, а не загальний скид.
Ввели міграцію: під час першого запуску нової версії старі записи перечитуються й перезаписуються з коректною політикою, без втрати сесії.
Що стало
| Метрика | До | Після |
|---|---|---|
| Частка збоїв читання даних | ~6% активної бази | нижче 0,3% |
| Час відновлення після релізу | 2-3 дні | години, за логами |
| Звернення до підтримки за тиждень | 340 | 40-50 |
Клас відмов, за якого оновлення вибивало користувача з акаунта, зник: релізи перестали бути ризиком для сесії, а підтримка розвантажилася без зростання команди.
Чим відрізняється Keychain від локального файлу та хмарної синхронізації?
Keychain — це системне захищене сховище, доступ до якого застосунок отримує не за шляхом до файлу, а через окремий програмний інтерфейс.
Запис прив'язується не до папки в пісочниці, а до ідентифікатора застосунку та його прав доступу, тому дані не лежать «просто файлом», який можна скопіювати або прочитати стороннім інструментом.
Система шифрує вміст і видає його лише тому процесу, чий підпис і права збігаються із заданими під час запису.
Локальний файл у пісочниці — найпростіший варіант, але він залишається звичайним файлом на диску. Він потрапляє до резервної копії пристрою, переноситься під час відновлення на новий пристрій і може бути вилучений, якщо зловмисник отримав доступ до файлової системи або до копії.
Хмарна синхронізація додає до цього ще один контур: дані йдуть на сторонній сервер, і їхній захист залежить уже від чужої інфраструктури та налаштувань акаунта.
Під час перевстановлення застосунку поведінка різниться принципово. Файли в пісочниці видаляються разом із застосунком — після встановлення заново їх немає.
Записи в Keychain за замовчуванням зберігаються, якщо явно не задано інше, тому застосунок може підняти збережену сесію або токен одразу після перевстановлення.
Це водночас і зручність, і ризик: якщо не керувати областю видимості записів, дані переживуть видалення застосунку, чого користувач не очікує.
Резервне копіювання теж керується явно. Для кожного запису задається політика доступності: наприклад, значення доступне лише при розблокованому пристрої або не переноситься до резервної копії взагалі.
Це дозволяє відокремити те, що має відновлюватися на новому пристрої, від того, що прив'язане до конкретного екземпляра і не повинно його покидати.
Підсумок: локальний файл — це дані, які легко втратити під час перевстановлення і легко винести разом із копією; хмара — зручна синхронізація, але із зовнішнім контуром довіри; Keychain — контрольоване сховище, де доступ, перенесення та відновлення задаються політиками на рівні кожного запису.
Технічні сумніви перед стартом робіт
Перед стартом майже кожна команда ставить один і той самий набір запитань: чи не зламаємо ми поточну логіку, що робити з уже збереженими даними, як поводитиметься iOS Keychain на різних версіях системи. Нижче — короткі відповіді щодо кожного пункту, щоб ви мали розуміння обсягу робіт ще до оцінки.
Чи сумісний iOS Keychain з нашим поточним кодом?
Так. Keychain — це системне сховище, а не окрема бібліотека, тому він не конфліктує з наявною архітектурою. Ми обгортаємо доступ у тонкий шар поверх поточних викликів: якщо у вас уже є власний модуль зберігання, він залишається точкою входу, а всередині змінюється реалізація. Публічні інтерфейси не переписуються, тому решта коду продовжує працювати без правок.
Що буде з даними, які вже збережені?
Міграція планується до першого релізу. Ми описуємо схему перенесення: читаємо старі значення, записуємо їх у Keychain із потрібними атрибутами доступу і лише після успішної перевірки видаляємо вихідне сховище.
Перенесення відбувається ідемпотентно — повторний запуск не псує дані, а на час переходу обидва джерела доступні паралельно.
Чи однаково це працює на різних версіях системи?
Базова поведінка стабільна, але частина атрибутів доступу та груп доступу з'явилася в новіших гілках системи. Ми перевіряємо цільову мінімальну версію і для старих збірок залишаємо сумісний набір параметрів.
На пристроях, де потрібний режим недоступний, шар зберігання перемикається на безпечний варіант без втрати функціональності.
Чи можна відкотитися, якщо щось піде не так?
Так, відкат закладається в план. Ми зберігаємо резервну копію вихідного сховища до міграції та тримаємо перемикач, що повертає застосунок до попередньої логіки читання. Це знімає ризик втрати даних під час оновлення і дозволяє випустити виправлення без термінового релізу.
Які ризики ви бачите на старті?
Основні — неповна міграція при перериванні запису та розбіжність поведінки на старих версіях системи. Перше закривається ідемпотентністю та перевіркою після перенесення, друге — тестовим прогоном на мінімальній підтримуваній збірці. Обидва сценарії перевіряються до викочування, а не після.
Обговоримо ваше завдання щодо захисту даних
Щоб повернутися з оцінкою та планом, а не із загальними словами, нам потрібно зрозуміти вашу поточну схему роботи з секретами в застосунку. Нижче — що уточнюємо на першій розмові та які вихідні дані допомагають порахувати обсяг робіт точно.
- Розбір завдання. З'ясовуємо, що саме зберігається в захищеному сховищі: токени доступу, паролі, ключі шифрування, біометричні відбитки. Від цього залежить вибір класу захисту та політики доступу.
- Інвентаризація поточного стану. Дивимося, як застосунок звертається до сховища зараз: через прямі виклики, через обгортку, чи є синхронізація між пристроями та групова доступність для розширень.
- Перевірка моделі загроз. Визначаємо, від кого захищаємося: від фізичного доступу до пристрою, від резервних копій, від джейлбрейку. Це задає рівень суворості політики.
- Збір вихідних даних. Просимо список сценаріїв, де дані записуються та читаються, вимоги до біометрії та терміну життя токенів, а також обмеження щодо сумісності з уже випущеними версіями.
- Оцінка та план. Формуємо перелік змін: міграція наявних записів, налаштування політик доступу, обробка помилок читання, тести на відновлення після перевстановлення.
- Узгодження наступного кроку. Фіксуємо порядок робіт, точки перевірки та те, що ви отримуєте на виході — робочий код, документацію з політик і звіт про перевірку сценаріїв.
Що надіслати для швидкої оцінки: опис поточної схеми зберігання, список сценаріїв роботи з даними та вимоги до безпеки. Опишіть завдання — повернемося з оцінкою та планом.







