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 с нужными атрибутами доступа и только после успешной проверки удаляем исходное хранилище. Перенос идёт идемпотентно — повторный запуск не портит данные, а на время перехода оба источника доступны параллельно.
Одинаково ли это работает на разных версиях системы?
Базовое поведение стабильно, но часть атрибутов доступа и групп доступа появилась в более новых ветках системы. Мы проверяем целевую минимальную версию и для старых сборок оставляем совместимый набор параметров. На устройствах, где нужный режим недоступен, слой хранения переключается на безопасный вариант без потери функциональности.
Можно ли откатиться, если что-то пойдёт не так?
Да, откат закладывается в план. Мы сохраняем резервную копию исходного хранилища до миграции и держим переключатель, возвращающий приложение к прежней логике чтения. Это снимает риск потери данных при обновлении и позволяет выпустить исправление без срочного релиза.
Какие риски вы видите на старте?
Основные — неполная миграция при прерывании записи и расхождение поведения на старых версиях системы. Первое закрывается идемпотентностью и проверкой после переноса, второе — тестовым прогоном на минимальной поддерживаемой сборке. Оба сценария проверяются до выката, а не после.
Обсудим вашу задачу по защите данных
Чтобы вернуться с оценкой и планом, а не с общими словами, нам нужно понять вашу текущую схему работы с секретами в приложении. Ниже — что уточняем на первом разговоре и какие исходные данные помогают посчитать объём работ точно.
- Разбор задачи. Выясняем, что именно хранится в защищённом хранилище: токены доступа, пароли, ключи шифрования, биометрические слепки. От этого зависит выбор класса защиты и политики доступа.
- Инвентаризация текущего состояния. Смотрим, как приложение обращается к хранилищу сейчас: через прямые вызовы, через обёртку, есть ли синхронизация между устройствами и групповая доступность для расширений.
- Проверка модели угроз. Определяем, от кого защищаемся: от физического доступа к устройству, от резервных копий, от джейлбрейка. Это задаёт уровень строгости политики.
- Сбор исходных данных. Просим список сценариев, где данные записываются и читаются, требования к биометрии и сроку жизни токенов, а также ограничения по совместимости с уже выпущенными версиями.
- Оценка и план. Формируем перечень изменений: миграция существующих записей, настройка политик доступа, обработка ошибок чтения, тесты на восстановление после переустановки.
- Согласование следующего шага. Фиксируем порядок работ, точки проверки и то, что вы получаете на выходе — рабочий код, документацию по политикам и отчёт о проверке сценариев.
Что прислать для быстрой оценки: описание текущей схемы хранения, список сценариев работы с данными и требования к безопасности. Опишите задачу — вернёмся с оценкой и планом.







