Безопасность в iOS приложениях

в 12:41, , рубрики: iOS разработка, swift, информационная безопасность, мобильная разработка, перевод, разработка мобильных приложений, разработка под iOS

Добрый день! Представляю вашему вниманию перевод статьи про базовые основы безопасности конфиденциальных данных в iOS приложениях «Application Security Musts for every iOS App» автора Arlind Aliu.

Безопасность приложений – один из самых важных аспектов разработки программного обеспечения. Пользователи приложений надеются, что информация, которую они предоставляют, надежно защищена. Поэтому нельзя так просто предоставлять кому-либо конфиденциальную информацию.

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

Хранение данных не в том месте

Я провел исследование нескольких приложений из AppStore и во многих допускается одна и та же ошибка: конфиденциальная информация хранится там, где её не должно быть.
Если вы храните данные личного характера в UserDefaults, то вы подвергаете её риску. UserDefaults хранятся в файле со списком свойств, который находится внутри папки «Настройки» в вашем приложении. Данные сохраняются в приложении без малейшего намека на шифрование.

Установив на mac стороннюю программу, как например iMazing, можно даже не взламывать телефон, а сразу увидеть все данные UserDefaults, из приложения установленного из AppStore. Такие программы позволяют смотреть и управлять данными из приложений, установленных на айфоне. Можно легко получить UserDefaults любого приложения.
В этом и заключается главная причина, по которой я решил написать статью – я нашел кучу приложений в AppStore, которые хранят данные в UserDefaults, такие как: токены, активные и возобновляемые подписки, число доступных денег и так далее. Все эти данные можно легко получить и использовать со злым умыслом, начиная от управления платными подписками в приложении и заканчивая взломом на сетевом уровне и хуже.

А теперь о том, как нужно хранить данные.

Запомните, в UserDefaults следует хранить только небольшой объём информации, такой как настройки внутри приложения, то есть данные, которые не являются конфиденциальными для пользователя.

Пользуйтесь специальными службами безопасности от Apple для того, чтобы хранить личные данные. API сервис Keychain позволяет хранить некоторый объём данных пользователя в зашифрованной базе данных. Там вы можете хранить пароли и прочие важные для пользователя данные, как например информация о кредитной карте, или даже небольшие важные заметки.
Так же там могут находиться зашифрованные ключи и сертификаты, с которыми вы работаете.

API сервис Keychain

Ниже приведен пример того, как можно сохранить пароль пользователя в Связке ключей.

class KeychainService {
	    func save(_ password: String, for account: String) {
	        let password = password.data(using: String.Encoding.utf8)!
	        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
	                                    kSecAttrAccount as String: account,
	                                    kSecValueData as String: password]
	        let status = SecItemAdd(query as CFDictionary, nil)
	        guard status == errSecSuccess else { return print("save error")
	    }
	}

Часть словаря kSecClass:kSecClassGenericPassword означает что информацией, нуждающейся в шифровании является пароль. Затем мы добавляем новый пароль в связку ключей вызывая метод SecItemAdd. Получение данных из связки аналогично сохранению.

func retrivePassword(for account: String) -> String? {
	    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
	                                kSecAttrAccount as String: account,
	                                kSecMatchLimit as String: kSecMatchLimitOne,
	                                kSecReturnData as String: kCFBooleanTrue] 
	    
	    var retrivedData: AnyObject? = nil
	    let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData) 
	    
	    guard let data = retrivedData as? Data else {return nil}
	    return String(data: data, encoding: String.Encoding.utf8)
	}

Давайте напишем небольшую проверку корректности сохранения и получения данных.

func testPaswordRetrive() {
	    let  password = "123456"
	    let account = "User"
	    keyChainService.save(password, for: account)
	    XCTAssertEqual(keyChainService.retrivePassword(for: account), password)
	}

На первый взгляд может показаться, что Keychain API довольно сложно использовать, особенно если нужно сохранить больше одного пароля, поэтому я призываю вас использовать паттерн Фасад для этих целей. Он позволит сохранять и модифицировать данные в зависимости от нужд приложения.

Если хотите узнать больше про этот паттерн, а также о том, как создать простенькую обертку для сложных подсистем, тогда вот эта статья поможет вам. Так же в интернете полно открытых библиотек, помогающих использовать Keychain API, например, SAMKeychain и SwiftKeychainWrapper.

Сохранение пароля и Авторизация

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

Если вы храните данные в UserDefault, то прочитав информацию из первой части статьи, вы уже понимаете, как сильно вы рискуете. Сохраняя пароли в Связке ключей вы серьезно повышаете уровень безопасности вашего приложения, но опять же, прежде чем сохранять где-либо конфиденциальную информацию, нужно её предварительно зашифровать.

Предположим, что некий хакер может атаковать нас через нашу сеть. Таким образом он получит пароли в виде сырого текста. Лучше, конечно, хэшировать все пароли.

Шифрование личных данных

Хэширование может показаться чересчур сложным занятием, если делать все самому, поэтому в данной статье мы воспользуемся библиотекой CryptoSwift. В ней собрано много стандартных надежных алгоритмов шифрования, применяемых в Swift.

Давайте попробуем сохранить и извлечь пароль из связки ключей используя алгоритмы CryptoSwift.

func saveEncryptedPassword(_ password: String, for account: String) {
	    let salt = Array("salty".utf8)
	    let key = try! HKDF(password: Array(password.utf8), salt: salt, variant: .sha256).calculate().toHexString()
	    keychainService.save(key, for: account)
	}

Приведенная выше функция записывает логин и пароль и сохраняет их в Keychain в виде зашифрованной строки.

Давайте разберемся, что же происходит внутри:

— В переменную salt записываются логин и пароль в виде строки
— sha256 заполняет хэш SHA-2
— HKDF это функция формирования ключа (KDF), основанная на коде аутентификации сообщений (HMAC)

Мы создали переменную salt для того, чтобы усложнить хакерам задачу. Мы могли бы зашифровать только пароль, но в таком случае у злоумышленника может быть список самых часто используемых паролей, он без проблем зашифрует их и сравнит с нашим зашифрованным паролем. После чего найти пароль к конкретному аккаунту не составит труда.
Теперь же мы можем авторизоваться, используя наш аккаунт и созданный ключ.

authManager.login(key, user)

Конечно, сервер должен знать, что зашифровано в нашей переменной salt. Бэкэнд сможет сравнить ключи используя тот же алгоритм, чтобы идентифицировать пользователя.
Используя такой подход, вы сильно повышаете безопасность вашего приложения.

В качестве завершения

Никогда не пренебрегайте безопасностью вашего приложения. В данной статье мы, прежде всего, разобрались какие могут быть последствия при хранении конфиденциальных данных в UserDefaults и для чего нужен Keychain.

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

Автор: kosyakus

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js