Civil &: строительная инженерия
Внедрение безопасного хранения данных с помощью Keychain в Ios
Table of Contents
Введение в безопасное хранение данных на iOS
Защита конфиденциальных пользовательских данных является фундаментальной обязанностью любого приложения iOS. Независимо от того, храните ли вы токены аутентификации, ключи шифрования или личные учетные данные, платформа предоставляет специальное аппаратное решение: Keychain. В отличие от или файлов списка свойств, Keychain шифрует данные в состоянии покоя и обеспечивает строгие средства контроля доступа. Эта статья предоставляет всеобъемлющее руководство по внедрению безопасного хранения данных с Keychain, охватывая как нативную структуру безопасности, так и практические лучшие практики для производственных приложений.
Понимание ключей iOS
Keychain - это безопасный контейнер для хранения, управляемый операционной системой. Он хранит небольшие, чувствительные элементы, такие как пароли, криптографические ключи или сертификаты, в зашифрованной базе данных. Данные, записанные в Keychain, защищены даже при блокировке устройства. Основные возможности включают в себя:
- Шифрование в покое с использованием аппаратно-поддерживаемого AES-256.
- Управление доступом через пароль устройства, Touch ID или Face ID.
- Настойчивость в переустановке приложений (если настроен) и необязательная синхронизация iCloud.
- Изоляция между приложениями: по умолчанию одно приложение не может читать элементы другой цепочки ключей, если они не разделяют группу доступа к цепочке ключей.
Keychain не предназначен для больших сгустков; держите каждый элемент под несколькими килобайтами. Для больших данных рассмотрите возможность использования API или фреймворка вместе с шифрованием на основе файлов.
Keychain Services API против сторонних библиотек
Apple предоставляет нативный API Keychain Services, который является мощным, но многословным. Вы можете использовать его напрямую или принять удобную для Swift обертку. Популярные сторонние библиотеки, такие как KeychainAccess или SwiftKeychainWrapper, уменьшают количество шаблонов. Однако понимание базового API имеет важное значение для отладки и когда вам нужен мелкозернистый контроль над политиками доступа. Сегодня мы сосредоточимся на нативном API со Swift.
Настройка Keychain хранилища
Перед хранением чего-либо вы должны выбрать класс элементов Keychain . Наиболее распространенным для общих паролей является . Для паролей или сертификатов Интернета существуют другие классы. Каждый элемент ссылается на набор атрибутов — словарь (CFDictionary), который описывает элемент.
Основной поток всегда следует этой схеме:
- Создайте словарь запросов с классом предметов и атрибутами.
- Назовите соответствующие функции , , , .
- Проверить возвратный или код ошибки.
Перед написанием кода импортируйте модуль безопасности:
import Security
import Foundation // for Data and String utilities
Хранение данных в Keychain
Написание общего пароля
Чтобы сохранить токен (например, JWT) для текущего пользователя:
func saveToken(_ token: String, forAccount account: String) -> Bool {
guard let tokenData = token.data(using: .utf8) else { return false }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: tokenData,
// Optional: restrict access to when device is unlocked
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
// Delete any existing item first to avoid duplicates
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
Ключевые моменты:
- выступает в качестве первичного ключа; выберите уникальную строку (например, идентификатор пользователя или константу, подобную ).
- управляет, когда элемент может быть прочитан. Для лучшей безопасности; он предотвращает резервное копирование iCloud и ограничивает доступ к текущему устройству.
- Мы звоним перед добавлением, чтобы избежать накопления дубликатов.В качестве альтернативы вы можете использовать .
Добавление контроля доступа (биометрия или пароль)
Для высокочувствительных данных требуется Touch ID или Face ID перед чтением:
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.userPresence, // requires passcode, Face ID, or Touch ID
nil
)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: tokenData,
kSecAttrAccessControl as String: accessControl as Any
]
SecItemAdd(query as CFDictionary, nil)
Теперь любой вызов для этого элемента вызовет биометрический или парольный код. Используйте от LocalAuthentication, чтобы изящно обрабатывать взаимодействие с пользователем.
Получение данных из Keychain
Чтобы прочитать сохраненный токен:
func retrieveToken(forAccount account: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess,
let data = item as? Data,
let token = String(data: data, encoding: .utf8) else {
return nil
}
return token
}
Настройте на , чтобы получить данные обратно. Используйте , чтобы получить один результат. Если вы опустите предел, API может вернуть массив.
Важно: При использовании контроля доступа (биометрии) вызов может вернуться , если пользователь отменит. Обработайте этот случай отдельно и никогда не возвращайтесь в обычное хранилище текста.
Обновление и удаление элементов ключевых цепочек
Обновление существующего пункта
Вместо удаления и повторного добавления используйте :
func updateToken(_ newToken: String, forAccount account: String) -> Bool {
guard let newData = newToken.data(using: .utf8) else { return false }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account
]
let attributesToUpdate: [String: Any] = [
kSecValueData as String: newData
]
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
return status == errSecSuccess
}
Это более эффективно, чем удаление + добавление, и это позволяет избежать потенциальных условий гонки.
Удаление пункта
func deleteItem(forAccount account: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account
]
let status = SecItemDelete(query as CFDictionary)
return status == errSecSuccess
}
Будьте осторожны, чтобы не удалять элементы, которые принадлежат другим приложениям, которые разделяют одну и ту же группу доступа, всегда размещайте свой запрос с , если вы используете общие цепочки ключей.
Атрибуты контроля доступа и доступности
Константа определяет , когда элемент Keychain может быть прочитан.Выберите наиболее ограничительный вариант, который все еще соответствует потребностям вашего приложения:
| Attribute | Meaning |
|---|---|
kSecAttrAccessibleWhenUnlocked | Available only while device is unlocked (default). |
kSecAttrAccessibleAfterFirstUnlock | Available after device boots and is unlocked once. Allows background access. |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Requires a passcode to be set. Strictest option—prevents access even after unlock if passcode is removed. |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | Same as WhenUnlocked but does not back up to iCloud, and cannot be restored to another device. |
Для большинства приложений устанавливается правильный баланс между безопасностью и удобством использования. Если вам нужно прочитать элементы в фоновом режиме (например, маркер обновления фона), вы должны использовать (и принять, что данные немного менее защищены).
Обработка ошибок и общие подводные камни
Функции возвращают . Всегда проверяйте его и обрабатывайте сбои надлежащим образом. Обычные ошибки:
- (-25300) — Ни один пункт не соответствует запросу.
- (–25299) – элемент с тем же первичным ключом уже существует (если вы не удалили его первым).
- (–128) – пользователь отменил биометрическую подсказку.
- (-25293) — Аутентификация не удалась или биометрия недоступна.
Никогда не игнорируйте статус неуспешности. Грациозно ухудшайте: покажите сообщение об ошибке или повторите попытку, но никогда не храните конфиденциальные данные за пределами Keychain в качестве резервного копирования. Вы можете использовать для проверки биометрической доступности перед попыткой доступа.
Лучшие практики и производственные соображения
- Используйте уникальные, описательные имена учетных записей для каждого пользователя или типа элемента, чтобы избежать столкновений.
- Всегда указывается атрибут доступности ; в противном случае применяется системный по умолчанию , который может быть не идеальным.
- Очистить данные Keychain, когда пользователь выходит из системы — проверьте все известные учетные записи и удалите элементы.
- Используйте группы доступа к ключевым цепочкам только при совместном использовании между вашими собственными приложениями.
- Никогда не храните нечувствительные данные (например, предпочтения пользователя) в цепочке ключей — используйте или базу данных вместо этого.
- Рассматривайте использование с для продвинутых сценариев (macOS Catalyst).
- Тест на реальном устройстве ; симулятор использует программную цепочку ключей, которая ведет себя иначе, чем аппаратное хранилище.
Использование Keychain с помощью SwiftUI и Async/Await
Для современных приложений оберните операции Keychain в классе асинхронизации, чтобы избежать блокировки основной нити. Пример с использованием :
actor KeychainManager {
func saveToken(_ token: String, for account: String) async -> Bool {
// same implementation as above, but now it's safe to call from any context
return saveToken(token, forAccount: account)
}
}
Если вы используете биометрические данные, вызов может блокировать поток в ожидании взаимодействия с пользователем. Оберните его в фоновую очередь или, лучше, используйте метод перед вызовом Keychain.
Заключение
The iOS Keychain is the correct place to store small, sensitive pieces of data. By using the native Keychain Services API, you gain direct control over encryption, accessibility, and authentication policies. Always pair your Keychain usage with solid error handling and remember to clear data when appropriate. For further reading, refer to the Apple Keychain Service Documentation and the Keychain Concepts overview. Adopting these practices will help you ship iOS apps that respect user privacy and withstand security scrutiny.