Хранение sensitive-информации в KeyChain
![]() |
Критичность: ИНФО |
Способ обнаружения: DAST, SENSITIVE INFO |
Описание
KeyChain — небольшое защищенное хранилище данных, которое предоставляет операционная система iOS. Фактически представляет собой SQLite-базу с зашифрованными данными (/private/var/Keychains/keychain-2.db) и является одним из рекомендуемых способов хранения конфиденциальной информации пользователя на устройстве. Несмотря на то, что KeyChain зашифровано, есть несколько способов получения его содержимого с устройства:
- Зашифрованный локальный бэкап устройства. При установке пароля при создании локальной резервной копии устройства в него также попадают и элементы KeyChain, которые хранятся на устройстве, в том числе и данные приложений.
- Jailbreak. При возможности осуществить процедуру Jailbreak благодаря специальным инструментам появляется возможность просмотреть и выгрузить все элементы KeyChain в открытом виде.
- Облачное хранилище KeyChain. Благодаря опции синхронизации элементов хранилища между несколькими устройствами, например между компьютером под управлением MacOS и устройством с iOS, данные попадают в облачные хранилища, а при компрометации последних — в руки злоумышленников.
- Данные KeyChain не очищаются после удаления приложения. При удалении приложения данные KeyChain сохраняются, в отличие от информации, находящейся в «песочнице» приложения (файловой системе). Если пользователь продаст устройство без сброса к заводским настройкам, покупатель может получить доступ к учетным записям приложений и данным предыдущего пользователя, просто установив приложение и посмотрев, что оно хранит в KeyChain (при условии, что разработчик не имплементировал очистку хранилища после переустановки приложения).
Именно поэтому не рекомендуется сохранять чувствительные данные пользователя в открытом виде в KeyChain, даже при условии его защищенности.
Рекомендации
Перед сохранением данных в KeyChain их рекомендуется шифровать или хэшировать в зависимости от способа дальнейшего использования и необходимости получения исходного значения. Но тогда остро встает вопрос, а где в таком случае хранить ключ шифрования. В iOS есть механизм под названием Security Enclave. Он представляет собой отдельную защищенную безопасную среду на устройстве — это отдельный «безопасный мир» со своим процессором и небольшой операционной системой (включая ядро, драйвера, сервисы и приложения), доступа к которой нет ни у кого в системе, даже у основного процессора. Этот механизм широко используется самой операционной системой для хранения данных TouchID, FaceID, Apple Pay и т. д.
Благодаря тому, что операционная система Apple работает только на устройствах одного производителя, можно быть уверенным, что при запуске на устройстве с определенной версией iOS будет предоставлена возможность хранения ключей в безопасном аппаратном хранилище. Достаточно обратиться к этому механизму, сгенерировать там ключи и зашифрованное значение поместить в Keychain.
В качестве примера использования рассмотрим интересную библиотеку EllipticCurveKeyPair. Первое, что необходимо сделать, это добавление зависимости в Сocoapods
pod EllipticCurveKeyPair
или в Carthage
github "agens-no/EllipticCurveKeyPair"
Далее создадим экземпляр менеджера, который в дальнейшем будет управлять ключами и служить интерфейсом для всех операций.
struct KeyPair {
static let manager: EllipticCurveKeyPair.Manager = {
let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
let config = EllipticCurveKeyPair.Config(
publicLabel: "payment.sign.public",
privateLabel: "payment.sign.private",
operationPrompt: "Confirm payment",
publicKeyAccessControl: publicAccessControl,
privateKeyAccessControl: privateAccessControl,
token: .secureEnclave)
return EllipticCurveKeyPair.Manager(config: config)
}()
}
Также, если по какой-то причине Secure Enclave недоступен, можно использовать KeyChain.
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
}())
Собственно, любые операции теперь можно совершать, используя KeyPair.manager.
Шифрование
do {
let digest = "some text to encrypt".data(using: .utf8)!
let encrypted = try KeyPair.manager.encrypt(digest, hash: .sha256)
} catch {
// handle error
}
Что интересно, можно шифровать данные на другом устройстве/ОС/платформе с помощью открытого ключа. Если хотите узнать все подробности о том, как шифровать в формате, который понимает Secure Enclave, рекомендуем ознакомиться с этой статьей. Как вариант, можно дополнительно шифровать важные данные перед их отправкой на устройство.
Расшифровка
do {
let encrypted = ...
let decrypted = try KeyPair.manager.decrypt(encrypted, hash: .sha256)
let decryptedString = String(data: decrypted, encoding: .utf8)
} catch {
// handle error
}
Подпись
do {
let digest = "some text to sign".data(using: .utf8)!
let signature = try KeyPair.manager.sign(digest, hash: .sha256)
} catch {
// handle error
}
Для подписи на закрытом ключе есть интересная идея использования — практически аналог двухфакторной аутентификации или один из вариантов усиления/дополнения этого механизма:
- Пользователь запрашивает соглашение, покупку или какую-либо операцию, которая требует его явного согласия.
- Сервер отправляет push-уведомление с токеном, который должен быть подписан.
- На устройстве подписываем токен при помощи закрытого ключа (что обязательно требует от пользователя подтверждения через FaceID/TouchID).
- Подписанный токен отправляется на сервер.
- Сервер при помощи открытого ключа проверяет подпись.
- Если все прошло успешно, можно быть уверенным, что пользователь действительно подтвердил покупку/соглашение с использованием биометрии.
Достаточно удобная библиотека, которая большинство функций берет на себя и предоставляет неплохие возможности.