Хранение секретов в связке ключей
Безопасно сохраняйте и получайте учётные данные.
«Хранение секретов в связке ключей» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Хранение секретов в связке ключей
- Управление доступом к связке ключей
- Биометрическая аутентификация
- Защита данных и шифрование