0Pricing
Swift Academy · Урок

Хранение секретов в связке ключей

Безопасно сохраняйте и получайте учётные данные.

«Хранение секретов в связке ключей» — бесплатный урок Swift Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Зачем нужна связка ключей

Пароли, токены и ключи никогда не должны храниться в UserDefaults или обычных файлах — их легко прочитать. Связка ключей — это зашифрованная база данных под управлением OS для небольших секретов, защищённая аппаратными средствами и кодом-паролем пользователя. Это единственное подходящее место для учётных данных.

import Security
// Keychain stores secrets encrypted at rest,
// survives app updates, and gates access by policy.

Элементы являются словарями

API службы связки ключей основан на C: элемент описывается словарём запроса типа [String: Any] с использованием ключей-констант kSec.... Одна и та же структура словаря используется для добавления, поиска, обновления и удаления.

import Security
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "user@example.com",
    kSecAttrService as String: "com.example.app"
]
_ = query

Классы элементов

Ключ kSecClass выбирает тип элемента. kSecClassGenericPassword предназначен для токенов и секретов приложения, а kSecClassInternetPassword хранит учётные данные сервера с атрибутами узла и протокола. Для большинства секретов приложения используется общий пароль.

import Security
// kSecClassGenericPassword   -> tokens, API keys
// kSecClassInternetPassword  -> server logins
// kSecClassKey / Certificate -> crypto material
let cls = kSecClassGenericPassword
_ = cls

Добавление элемента

SecItemAdd вставляет новый элемент. Включите данные под ключом kSecValueData в виде Data, а также атрибуты, однозначно его определяющие. Метод возвращает OSStatus; значение errSecSuccess означает успешное выполнение.

import Security
func save(_ token: String, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecValueData as String: Data(token.utf8)
    ]
    return SecItemAdd(query as CFDictionary, nil)
        == errSecSuccess
}

Обработка дубликатов

При добавлении элемента с уже существующими определяющими атрибутами возвращается errSecDuplicateItem. Надёжное сохранение сначала вызывает SecItemAdd, а при обнаружении дубликата использует SecItemUpdate — это шаблон upsert.

import Security
func upsert(_ data: Data, account: String) -> Bool {
    let base: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    var add = base
    add[kSecValueData as String] = data
    let status = SecItemAdd(add as CFDictionary, nil)
    if status == errSecDuplicateItem {
        return SecItemUpdate(base as CFDictionary,
            [kSecValueData as String: data] as CFDictionary)
            == errSecSuccess
    }
    return status == errSecSuccess
}

Обратное чтение элемента

SecItemCopyMatching выполняет поиск. Чтобы получить байты секрета, необходимо установить kSecReturnData в значение «истина», а kSecMatchLimit — в kSecMatchLimitOne. Результат возвращается через выходной параметр типа CFTypeRef.

import Security
func load(account: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne]
    var result: CFTypeRef?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result)
    guard status == errSecSuccess else { return nil }
    return result as? Data
}

Обновление элемента

SecItemUpdate принимает два словаря: запрос, находящий элемент, и словарь атрибутов для обновления. Изменяются только перечисленные Вами атрибуты; всё остальное сохраняется.

import Security
func update(_ newData: Data, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let attrs: [String: Any] = [
        kSecValueData as String: newData]
    return SecItemUpdate(query as CFDictionary,
        attrs as CFDictionary) == errSecSuccess
}

Удаление элемента

SecItemDelete удаляет совпадающие элементы. Удаление несуществующего элемента возвращает errSecItemNotFound; при очистке учётных данных после выхода из системы это можно считать успешным результатом.

import Security
func delete(account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let status = SecItemDelete(query as CFDictionary)
    return status == errSecSuccess
        || status == errSecItemNotFound
}

Расшифровка OSStatus

Ошибки представлены целочисленными кодами OSStatus. SecCopyErrorMessageString преобразует такой код в понятное человеку описание, что особенно полезно при выяснении причин сбоя сохранения или чтения.

import Security
func describe(_ status: OSStatus) -> String {
    return SecCopyErrorMessageString(status, nil)
        as String? ?? "OSStatus \(status)"
}

Уникальная идентификация элементов

Элементы сопоставляются по сочетанию атрибутов — обычно по kSecAttrService и kSecAttrAccount для универсальных паролей. Выбирайте стабильную строку службы, предназначенную именно для приложения, чтобы разные секреты никогда не конфликтовали.

import Security
// Uniqueness for generic passwords usually comes from:
//   service (your bundle id) + account (the username)
let service = "com.example.app.auth"
let account = "current-user"
_ = (service, account)

Небольшая оболочка

Поскольку низкоуровневый программный интерфейс многословен, команды обычно оборачивают его в небольшой тип, предоставляющий save, read и delete. Так вся служебная часть работы с kSec сосредоточена в одном месте, а места вызова остаются аккуратными.

import Security
struct TokenStore {
    let service = "com.example.app.auth"
    func read(_ account: String) -> Data? {
        let q: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne]
        var out: CFTypeRef?
        return SecItemCopyMatching(q as CFDictionary, &out)
            == errSecSuccess ? out as? Data : nil
    }
}

Быстрая проверка

Вспомните правильный способ получить байты сохранённого секрета.

Итоги

Вы изучили операции создания, чтения, обновления и удаления в Связке ключей:

  • Храните учётные данные в зашифрованной Связке ключей, а не в UserDefaults или файлах.
  • Описывайте элементы с помощью словарей запросов kSec; выбирайте класс, например kSecClassGenericPassword.
  • SecItemAdd / SecItemCopyMatching / SecItemUpdate / SecItemDelete охватывают весь жизненный цикл; обрабатывайте errSecDuplicateItem обновлением существующего элемента или добавлением нового.
  • Идентифицируйте элементы по service + account и расшифровывайте OSStatus для отладки.

Часто задаваемые вопросы

Урок «Хранение секретов в связке ключей» бесплатный?

Да — полный текст урока «Хранение секретов в связке ключей» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «Хранение секретов в связке ключей»?

Безопасно сохраняйте и получайте учётные данные. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Хранение секретов в связке ключей»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Хранение секретов в связке ключей
  2. Управление доступом к связке ключей
  3. Биометрическая аутентификация
  4. Защита данных и шифрование
← Назад к Swift Academy