Как в 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).

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся хуйня с паролями и токенами в iOS... Ну, народ-то у нас какой? Правильно, распиздяйский. Запихнут в UserDefaults номер своей банковской карты и думают — «ой, ну кто там полезет».

Так вот, блядь, на джейлбрейкнутом телефоне твои UserDefaults или файлы в Documents — это как открытый дневник на столе в кабаке. Любой мудак с консолью за две секунды всё вытащит. Пиздец и срамота.

А правильный путь, он один — Keychain Services. Это, сука, не твоя песочница, это святая святых системы. Операционка сама этим рулит, шифрует на аппаратном уровне, и даже если девайс у тебя из рук вырвут — нихуя не прочитают. Ну, если ты, конечно, не лох и не поставил доступ «когда угодно».

Работать с ним — это не UserDefaults.set(_:forKey:), тут надо мозги включать. Берёшь фреймворк Security и начинаешь шаманить со словарями атрибутов. Это как к банковскому сейфу подбирать отмычки, только наоборот — ты его закрываешь.

Вот смотри, как, например, номер военного билета спрятать, чтобы ни одна мартышлюшка не добралась:

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
}

Видишь этот kSecAttrAccessibleWhenUnlockedThisDeviceOnly? Это, блядь, главная фишка. Данные будут доступны только когда устройство разблокировано, и только на этом устройстве. В iCloud эту хуйню не засинхронизирует, даже если очень попросить. Максимальная защита, ёпта.

А чтобы потом достать этот самый билет обратно — вот тебе ритуал:

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
}

И главное, чувак — не выёбывайся. Если хранишь персональные данные пользователей, ты уже в зоне ответственности. GDPR и прочие регуляции — это не хуй собачий, за нарушение могут так впендюрить, что мало не покажется. Думай головой, что и зачем ты прячешь. А Keychain — он тебе в этом лучший друг, если, конечно, пользоваться им с умом, а не как попало.