Keychain API в iOS

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Всем привет!
Не так давно столкнулся с необходимостью использования Keychain-а для обеспечения дополнительной защиты при входе в приложение.
Я нашел много хороших статей по этой теме, но в основном там описываются сторонние фреймворки, упрощающие жизнь, а было интересно посмотреть, как работать с API напрямую. В этой статье я попытался объединить документацию Apple с практикой на простом примере.

Начнем с небольших определений


Keychain — зашифрованная база данных, куда сохраняются небольшие объемы пользовательской информации (см. документацию Apple).
Общая схема работы продемонстрирована на рисунке.
image
Keychain API Services в свои очередь являются часть фреймворка Security, но его рассмотрение требует отдельной статьи.

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


let keychainItemQuery = [
     kSecValueData: pass.data(using: .utf8)!,
     kSecClass: kSecClassGenericPassword
 ] as CFDictionary

 let status = SecItemAdd(keychainItemQuery, nil)
 print("Operation finished with status: \(status)")

Выше приведен пример сохранения пароля в Keychain.
Рассмотрим функцию SecItemAdd подробнее.
func SecItemAdd(_ attributes: CFDictionary, 
              _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus

На вход подается объект класса CFDictionary, который в свою очередь является ссылкой на неизменяемый объект словаря.
Что в это словарь входит? На самом деле его состав зависит от решаемой задачи — здесь же мы просто сохраняем простой пароль, давайте разберем этот простейший запрос.
Итак, kSecClass — этот ключ используется для значений хранимых элементов, список их можно посмотреть тут, мы же выбрали стандартный пароль.
kSecValueData — ключ, использующийся для передачи данных элемента.
На этом обязательные ключи заканчиваются, далее идут опциональные. Список таких параметров доступен в документации.
Возвращаемое значение типа OSStatus определяет результат операции сохранения/изменения/удаления, у него так же есть масса значений.

Получение элемента


Для получения элемента из Keychain-а используется метод SecItemCopyMatching.
Формируем запрос в виде словаря, где содержится искомый пароль.

let keychainItem = [
     kSecValueData: pass.data(using: .utf8)!,
     kSecClass: kSecClassGenericPassword,
     kSecReturnAttributes: true,
     kSecReturnData: true
 ] as CFDictionary
        
 var ref: AnyObject?

 let status = SecItemCopyMatching(keychainItem, &ref)
 if let result = ref as? NSDictionary, let passwordData = result[kSecValueData] as? Data {
     print("Operation finished with status: \(status)")
     print(result)
     let str = String(decoding: passwordData, as: UTF8.self)
     print(str)
 }

Посмотрим логи:
Operation finished with status: 0
{
accc = "<SecAccessControlRef: ak>";
acct = "";
agrp = "xxx.com.maximenko.xxx";
cdat = "2020-11-19 20:39:43 +0000";
mdat = "2020-11-19 20:39:43 +0000";
musr = {length = 0, bytes = 0x};
pdmn = ak;
persistref = {length = 0, bytes = 0x};
sha1 = {length = 20, bytes = 0xxxxxxxxxxxxxxxxxxxxxxx};
svce = "";
sync = 0;
tomb = 0;
"v_Data" = {length = 3, bytes = 0x4b656b};
}
Kek

Как мы видим, возвращен 0, символизирующий успешный результат поиска и выведен весь список атрибутов, полученных из API (Access group параметр затерт на всякий случай:)). Эти атрибуты подробно описаны тут.
Значение по ключу kSecValueData нас собственно тут интересует, успешно разворачиваем его в строку, далее выведенную терминал.

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


Для этого есть метод SecItemUpdate.
На вход подается 2 CFDictionary словаря — в первом информация об обновляемом элементе, во втором — та информация, на которую надо будет заменить старую.
let query = [
     kSecClass: kSecClassGenericPassword,
] as CFDictionary

let updateFields = [
     kSecValueData: pass.data(using: .utf8)!
] as CFDictionary

let status = SecItemUpdate(query, updateFields)

На примере простого пароля — в первом запросе указываем что именно требуется найти для замены, во втором — то ключ-значение, которое мы обновим в найденном элементе.

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


Для удаления используем SecItemDelete.
У него на входе один параметр — словарь c информацией об удаляемом элементе, который надо найти.
Возвращает статус выполнения операции типа OSStatus.
let query = [
     kSecClass: kSecClassGenericPassword,
     kSecValueData: pass.data(using: .utf8)!
] as CFDictionary

let res = SecItemDelete(query)


Подведение итогов


В данной статье рассматривается работа с Keychain-ом на примере нескольких основных методов. Если увидите какие-то неточности, ошибки или просто хотите более подробно обсудить тему, пишите в комментарии или Telegram (skipperprivate).

P.S.
Если будет интересно, можно отдельную статью посвятить работе с более сложными сущностями вроде Интернет-пароля, с большим количеством полей и т.д., или обсудить подобное в комментариях.
Источник: https://habr.com/ru/post/526510/


Интересные статьи

Интересные статьи

Одна из самых важных (на мой взгляд) функций в Битрикс24 это бизнес-процессы. Теоретически они позволяют вам полностью избавиться от бумажных служебок и перенести их в эл...
В обновлении «Сидней» Битрикс выпустил новый продукт в составе Битрикс24: магазины. Теперь в любом портале можно создать не только лендинг или многостраничный сайт, но даже интернет-магазин. С корзино...
Возможность интеграции с «1С» — это ключевое преимущество «1С-Битрикс» для всех, кто профессионально занимается продажами в интернете, особенно для масштабных интернет-магазинов.
Если в вашей компании хотя бы два сотрудника, отвечающих за работу со сделками в Битрикс24, рано или поздно возникает вопрос распределения лидов между ними.
Cтатья будет полезна тем, кто думает какую выбрать CMS для интернет-магазина, сравнивает различные движки, ищет в них плюсы и минусы важные для себя.