Как хранить чувствительные данные пользователя: keychain, user defaults или file manager?
Что проверяют
Понимаете ли вы модель защиты данных iOS или заучили правило «пароли в Keychain». Вопрос уходит в детали: доступна ли запись после перезагрузки до первой разблокировки, переживает ли Keychain удаление приложения, как привязать секрет к Face ID.
Как отвечать
Разберите три варианта по назначению:
- UserDefaults: обычный plist в
Library/Preferences, отдельно не шифруется, попадает в бэкап и читается на устройстве с джейлбрейком. Только настройки и флаги, никаких токенов и паролей; - FileManager: файлы в песочнице, уровень защиты задаёт Data Protection:
.complete,.completeUntilFirstUserAuthentication(по умолчанию),.none. Ключи привязаны к коду-паролю, без пароля защита сводится к шифрованию диска. Годится для больших объёмов, чувствительное содержимое шифруйте сами черезCryptoKit(AES.GCM) с ключом из Keychain; - Keychain: хранилище под мелкие секреты, API из Security:
SecItemAdd,SecItemCopyMatching,SecItemUpdate, классkSecClassGenericPassword.
Главный атрибут это kSecAttrAccessible: WhenUnlocked закрывает доступ на заблокированном экране, AfterFirstUnlock нужен фоновым задачам и push. Суффикс ThisDeviceOnly запрещает переезд в бэкап и на другое устройство, kSecAttrAccessGroup открывает доступ расширениям.
Биометрия подключается через SecAccessControlCreateWithFlags с .userPresence или .biometryCurrentSet: второй флаг обнуляет доступ при добавлении нового отпечатка или лица. Ключи, которые не должны покидать устройство, генерируйте в Secure Enclave (kSecAttrTokenIDSecureEnclave, кривая P-256).
Финал: refresh-токен в Keychain с AfterFirstUnlock, access-токен только в памяти.
Где спотыкаются
Считают, что Keychain очищается вместе с приложением. Записи переживают удаление, и после переустановки пользователь оказывается уже залогинен. Лечится флагом первого запуска в UserDefaults.
Ставят WhenUnlocked, а потом ищут, почему фоновая синхронизация валится с errSecInteractionNotAllowed при заблокированном экране.
Пишут обёртку, которая молча теряет данные: повторный SecItemAdd возвращает errSecDuplicateItem и не перезаписывает, для обновления нужен SecItemUpdate.