Как в iOS-приложении можно безопасно хранить конфиденциальные данные, например, номер военного билета?

«Как в iOS-приложении можно безопасно хранить конфиденциальные данные, например, номер военного билета?» — вопрос из категории Софт-скиллы, который задают на 31% собеседований IOS Разработчик. Ниже — развёрнутый пример ответа, который можно адаптировать под свой опыт.

Ответ

В контексте iOS-разработки безопасное хранение конфиденциальных данных (паролей, токенов, номеров документов) реализуется с помощью Keychain Services. Keychain — это зашифрованное хранилище, управляемое операционной системой, доступ к которому строго контролируется.

Почему не UserDefaults или файлы?

  • UserDefaults и простые файлы хранятся в незашифрованном виде и легко извлекаются с джейлбрейкнутого устройства.
  • Keychain шифруется на аппаратном уровне, данные защищены даже при физическом доступе к устройству.

Основные шаги для работы с Keychain:

  1. Использовать фреймворк Security.
  2. Создать словарь (CFDictionary) запроса с атрибутами (класс, ключ аккаунта, данные).
  3. Вызывать функции SecItemAdd, SecItemCopyMatching, SecItemUpdate, SecItemDelete.

Пример сохранения номера "военного билета" в Keychain:

import Security

func saveMilitaryIDToKeychain(idNumber: String) -> Bool {
    let data = idNumber.data(using: .utf8)!

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword, // Класс элемента
        kSecAttrAccount as String: "military_id_account", // Уникальный ключ (логин)
        kSecAttrService as String: "com.yourapp.service", // Идентификатор сервиса
        kSecValueData as String: data, // Сами защищенные данные
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly // Атрибут доступа
    ]

    // Удаляем старую запись, если она есть, перед добавлением новой
    SecItemDelete(query as CFDictionary)

    let status = SecItemAdd(query as CFDictionary, nil)
    return status == errSecSuccess
}

Пример извлечения данных из Keychain:

func loadMilitaryIDFromKeychain() -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "military_id_account",
        kSecAttrService as String: "com.yourapp.service",
        kSecReturnData as String: true, // Запрашиваем данные
        kSecMatchLimit as String: kSecMatchLimitOne // Только одну запись
    ]

    var dataTypeRef: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

    guard status == errSecSuccess,
          let data = dataTypeRef as? Data,
          let idNumber = String(data: data, encoding: .utf8) else {
        return nil
    }
    return idNumber
}

Важные атрибуты безопасности:

  • kSecAttrAccessible: Определяет, когда доступны данные. kSecAttrAccessibleWhenUnlockedThisDeviceOnly — только когда устройство разблокировано, и данные не синхронизируются с iCloud (максимальная защита).
  • kSecAttrAccessGroup: Для обмена данными между приложениями одной команды.

Важно: Обработка таких данных должна строго соответствовать политике конфиденциальности приложения и законодательству (например, GDPR).