Безопасность приложений iOS: лучшие практики

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

В этой статье мы сосредоточимся на безопасности приложений iOS. Мы покажем вам конкретные методы повышения безопасности ваших приложений для iOS. Наши передовые методы охватывают средства для безопасного хранения данных, а также для отправки и получения данных по сети. Вы поймете, почему так сложно обеспечить правильную безопасность и как вы можете улучшить безопасность своего приложения, используя услуги Apple и других поставщиков.

В последнем сообщении блога мы обсудили безопасность приложений в целом как для iOS, так и для Android. В другом сообщении блога мы специально рассказываем о безопасности приложений Android.

Основы безопасности приложений iOS

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

Мы сосредоточимся на трех основных темах: безопасное хранение пользовательских данных, безопасная передача данных и использование новых криптографических API Apple.

Рекомендации по хранению пользовательских данных

Если вы разрабатываете приложения для iOS, ОС уже предоставляет множество функций безопасности. Все устройства iOS с процессором A7 или более поздней версии также имеют сопроцессор, называемый Secure Enclave. Он поддерживает функции безопасности iOS с аппаратным ускорением.

Песочница приложений Apple

Все приложения, работающие на iOS, работают в изолированной программной среде, чтобы убедиться, что приложение может получить доступ только к данным, которые хранятся в уникальном домашнем каталоге приложения. Если приложение хочет получить доступ к данным за пределами своего домашнего каталога, ему необходимо использовать службы, предоставляемые iOS, например, доступные для доступа к данным iCloud или фотоальбому. Поэтому никакое другое приложение не может читать или изменять данные из вашего приложения.

Песочница приложений Apple работает на основе разрешений пользователей UNIX и гарантирует, что приложения будут выполняться с менее привилегированным «мобильным» пользователем. Все, что находится за пределами домашнего каталога приложения, монтируется только для чтения. Все системные файлы и ресурсы защищены. Доступные API не позволяют приложениям повышать привилегии для изменения других приложений или самой iOS.

Для выполнения определенных привилегированных операций приложению необходимо объявить специальные права. Эти права подписываются вместе с приложением и не подлежат изменению. Примерами служб, которым требуются специальные права, являются HealthKit или аудиовход. Некоторые права даже ограничены для использования только в том случае, если Apple предоставит вам к ним доступ. Сюда входят такие сервисы, как CarPlay. Они сильнее защищены, потому что неправильное их использование может иметь фатальные последствия.

Помимо разрешений, дающих вам особые права, приложения могут использовать систему расширений iOS. У ОС есть много точек, которые могут использоваться расширениями приложений. Расширения приложений — это исполняемые файлы определенного назначения, связанные с приложением. Они работают в своем собственном адресном пространстве и управляются ОС.

Кроме того, в iOS есть методы предотвращения ошибок безопасности, связанных с памятью. Рандомизация макета адресного пространства (ASLR) рандомизирует назначенные области памяти для каждого приложения при каждом запуске. Это значительно снижает вероятность эксплуатации ошибок с повреждением памяти. Кроме того, страницы памяти помечаются как неисполняемые с помощью функции ARM Execute Never (XN), чтобы предотвратить выполнение вредоносного кода.

API защиты данных

Все версии iOS, начиная с iOS 4, имеют встроенную функцию безопасности под названием «Защита данных». Это позволяет приложению шифровать и расшифровывать файлы, хранящиеся в каталоге приложения. Процессы шифрования и дешифрования выполняются автоматически и с аппаратным ускорением. Защита данных доступна для API-интерфейсов файлов и баз данных, включая NSFileManager, CoreData, NSData и SQLite.

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

Четыре доступных уровня защиты включают в себя:

  • Нет защиты: файл всегда доступен и совсем не зашифрован
  • Выполнять до первой аутентификации пользователя: эта функция включена по умолчанию и расшифровывает файл после того, как пользователь впервые разблокирует свое устройство. После этого файл остается расшифрованным до перезагрузки устройства. Блокировка устройства не приводит к повторному шифрованию данных.
  • Завершить, если не открыт: файл шифруется до тех пор, пока приложение не откроет его в первый раз. Расшифровка остается в силе, даже когда устройство заблокировано пользователем.
  • Завершено: файл доступен только тогда, когда устройство разблокировано.

Вы можете указать уровень защиты при создании таких файлов:

try data.write(to: fileURL, options: .completeFileProtection)

Но вы также можете изменить уровень защиты существующих файлов, установив значения ресурсов:

try (fileURL as NSURL).setResourceValue( 
                  URLFileProtection.complete,
                  forKey: .fileProtectionKey)

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

Связка ключей

Связка ключей — это безопасное место для хранения небольших фрагментов данных. Это безопасное хранилище данных с аппаратным ускорением, которое шифрует все свое содержимое. Он используется системой для хранения таких данных, как пароли и сертификаты, но вы, как разработчик приложений, также имеете доступ к этому хранилищу данных.

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

Связка ключей — это безопасная замена хранилища ключей и значений для NSUserDefaults. NSUserDefaults вообще не шифруются, и их следует избегать для конфиденциальных данных.

Для каждого элемента цепочки для ключей вы можете определить определенные политики доступа для специальных возможностей и проверки подлинности. Вы можете потребовать присутствия пользователя (запрос разблокировки Face ID или Touch ID) или чтобы регистрация биометрического идентификатора не менялась с момента добавления элемента в связку ключей.

В качестве дополнительной функции связки ключей iOS вы можете решить, хотите ли вы хранить информацию в локальной связке ключей, которая доступна только на этом конкретном устройстве, или в связке ключей iCloud, которая синхронизируется на всех устройствах Apple. Это дает вам возможность обмениваться информацией между вашими коллегами на iPhone, iPad и Mac.

Несмотря на то, что Keychain Services API следует использовать везде, где это возможно, его интерфейс не совсем удобен для использования в Swift. Если вы планируете чаще использовать такую ​​функциональность в своей кодовой базе, подумайте о том, чтобы обернуть ее более удобным API Swift.

let query: [String: Any] = [
    kSecClass as String: kSecClassInternetPassword,
    kSecAttrAccount as String: account,
    kSecAttrServer as String: server,
    kSecValueData as String: password
]

let status = SecItemAdd(query as CFDictionary, nil)

Этот фрагмент кода хранит некоторые учетные данные для цепочки для ключей. Необходимо много преобразований, и, в конце концов, вы вызываете SecItemAdd, который синхронно сохраняет учетные данные и возвращает код состояния успеха или ошибки.

Лучшие практики для безопасной транспортировки данных

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

HTTPs

Большая часть сетевого взаимодействия осуществляется по протоколу HTTP между клиентом и сервером. По умолчанию HTTP-соединения не шифруются. Злоумышленники легко могут перехватить данные из вашей локальной сети или выполнить атаки «человек посередине».

Начиная с iOS 9 появилась новая функция под названием App Transport Security (ATS). Это повышает безопасность сетевого взаимодействия в ваших приложениях. ATS по умолчанию блокирует небезопасные соединения. Для этого требуется, чтобы все HTTP-соединения выполнялись с использованием HTTPS, защищенного с помощью TLS.

ATS можно настроить разными способами, чтобы ослабить эти ограничения. Поэтому вы можете разрешить небезопасные соединения HTTP для определенных доменов или изменить минимальную версию TLS, используемую для HTTPS.

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

Закрепление SSL

По умолчанию соединения HTTPS проверяются системой: она проверяет сертификат сервера и проверяет, действителен ли он для этого домена. Это должно убедиться, что подключенный сервер не является вредоносным. Однако у злоумышленников все еще есть способы выполнить более сложные атаки «человек посередине».

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

Для предотвращения подобных атак приложение может выполнять дополнительную проверку доверия к сертификатам сервера. Это называется SSL или закрепление сертификата. Вы можете реализовать закрепление SSL, включив список действительных сертификатов (или их открытых ключей или хэшей) в свой пакет приложений. Таким образом, приложение может проверить, находится ли сертификат, используемый сервером, в этом списке, и только затем связываться с сервером.

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

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

Всплывающее уведомление

Чтобы отправлять push-уведомления своим пользователям, вам необходимо использовать службы Apple APNS. Если вы хотите использовать сквозное шифрование или просто не хотите давать Apple (теоретический) шанс прочитать ваши сообщения, вы можете использовать расширения UNNotificationServiceExtension для изменения сообщений на стороне клиента.

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

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

class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
            fatalError("Cannot convert notification content to mutable content")
        }

        mutableContent.title = decryptText(mutableContent.title)
        mutableContent.body = decryptText(mutableContent.body)

        contentHandler(mutableContent)
    }

    // ...

}

Сквозное шифрование

Сквозное шифрование — это «Святой Грааль» для безопасной передачи данных. Это позволяет вам шифровать сообщения таким образом, что только отправитель и получатель могут их расшифровать, и ни Apple, ни ваши серверы не могут читать данные в открытом виде.

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

CloudKit

Если вашему приложению не нужен сервер, вы можете использовать CloudKit от Apple. CloudKit позволяет хранить данные в контейнерах iCloud, используя Apple ID в качестве механизма входа в приложение. Таким образом, вам не нужно внедрять все эти услуги самостоятельно.

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

Вся связь между вашим приложением и сервером может осуществляться с помощью платформы Apple CloudKit на стороне клиента. Если у вас есть дополнительное приложение для Android или веб-приложение, вы по-прежнему можете использовать CloudKit с помощью его JavaScript и веб-служб.

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

Использование криптографических API

iOS SDK включает в себя API-интерфейсы для решения общих криптографических задач. Как мы уже говорили выше, обычно рекомендуется полагаться на проверенные криптографические реализации, а не реализовывать их самостоятельно.

Apples CryptoKit — это новый API, который был представлен в iOS 13 и предоставляет API более низкого уровня для выполнения криптографических операций или реализации протоколов безопасности.

CryptoKit основан на более низкоуровневых API. Они были доступны и раньше, но создавали дополнительные факторы риска, поскольку разработчики часто использовали их неправильно.

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

Если вы хотите поддерживать более старые версии iOS, вы можете использовать эти API более низкого уровня или использовать известные сторонние библиотеки с открытым исходным кодом, такие как CryptoSwift.

Хэширование данных

Хеш-функции — это функции, которые преобразуют данные произвольного размера в значения фиксированного размера. Хорошие хеш-функции должны минимизировать дублирование выходных значений (так называемые коллизии) и быть очень быстрыми для вычислений. Swift включает функции хеширования в стандартной библиотеке Swift, но эти функции в значительной степени сосредоточены на скорости и имеют относительно высокую частоту конфликтов. Это делает их подходящими для операций, критически важных для производительности, но не для операций, связанных с безопасностью.

Безопасное хеширование данных просто с помощью CryptoKit. Вам просто нужно вызвать хеш-функцию для одной из ваших структур и выбрать хэш-алгоритм. CryptoKit поддерживает наиболее распространенные от SHA512 до SHA256.

let data = ...
let dataDigest = SHA512.hash(data: data)
let hashString = dataDigest.description

Аутентификация данных с использованием кодов аутентификации сообщений

Код проверки подлинности сообщения (MAC) используется для проверки подлинности отправителя сообщения и подтверждения целостности этого сообщения. Это позволяет получателю проверять происхождение сообщений и обнаруживать изменения в содержании сообщения.

Чтобы использовать такого рода проверки целостности, вы можете, например, использовать хешированные коды аутентификации CryptoSwift, также известные как HMAC. Структура HMAC — это общий тип, который можно использовать со всеми хеш-функциями, включенными в CryptoSwift.

Чтобы создать код аутентификации сообщения, вы можете использовать этот простой фрагмент кода.

let key = SymmetricKey(size: .bits256)

let authenticationCode = HMAC<SHA256>.authenticationCode(for: messageData, using: key)

Вы можете проверить сообщение, используя:

let isValid = HMAC<SHA256>.isValidAuthenticationCode(
    Data(authenticationCode),
    authenticating: messageData,
    using: key
)

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

Шифрование данных с использованием симметричных ключей

Шифровать и расшифровывать данные с помощью симметричного ключа тоже просто. Вы можете использовать один из двух доступных шифров: ChaChaPoly (ChaCha20-Poly1305) или AES-GCM в CryptoSwift:

let encryptedData = try! ChaChaPoly.seal(data, using: key).combined
let sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData)
let decryptedData = try! ChaChaPoly.open(sealedBox, using: key)

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

Исполнение ключевого соглашения

Во многих случаях вам потребуется выполнить некоторую форму обмена ключами для обмена криптографическими ключами по небезопасному каналу. С помощью протоколов согласования ключей, таких как эллиптическая кривая Диффи-Хеллмана (ECDH), вы можете получить общий секрет.

1 Создайте пары закрытый/открытый ключ для Алисы и Боба

let alicePrivateKey = P256.KeyAgreement.PrivateKey()
let alicePublicKey = alicePrivateKey.publicKey

let bobPrivateKey = P256.KeyAgreement.PrivateKey()
let bobPublicKey = bobPrivateKey.publicKey

И Алиса, и Боб создают свои собственные пары закрытый/открытый ключ и делятся своими открытыми ключами.

2. Получение общего секрета

let aliceSharedSecret = try! alicePrivateKey.sharedSecretFromKeyAgreement(with: bobPublicKey)

let bobSharedSecret = try! bobPrivateKey.sharedSecretFromKeyAgreement(with: alicePublicKey)

Теперь мы смогли получить общий секрет, используя собственный закрытый ключ вместе с открытым ключом контрагента. aliceSharedSecret и bobSharedSecret теперь одинаковы.

3. Созданный секретный номер сам по себе не должен использоваться в качестве ключа шифрования. Вместо этого его можно использовать для создания гораздо более крупного и более безопасного ключа шифрования с использованием вывода ключа HKDF или X9.63.

let usedSalt = "Secure iOS App".data(using: .utf8)!

let symmetricKey = aliceSharedSecret.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: protocolSalt,
    sharedInfo: Data(),
    outputByteCount: 32
)

Вот так вообще можно было реализовать! Также обратите внимание, что у Apple есть реализация Диффи-Хеллмана в iOS как часть Secure Transport. Вот Справочник по безопасному транспорту.

Создание и проверка подписей

Если вы хотите отправлять сообщения и убедиться, что отправитель — это тот человек, за которого вы его принимаете, вы можете использовать подписи. Для этого вам сначала понадобится пара закрытый/открытый ключ.

let signingKey = Curve25519.Signing.PrivateKey()
let signingPublicKey = signingKey.publicKey

С помощью закрытого ключа можно подписать любую форму данных.

let data = ...
let signature = try! signingKey.signature(for: data)

Затем эта подпись отправляется вместе с фактическими данными получателю, который может использовать открытый ключ для проверки подписи.

let isSignatureValid = signingPublicKey.isValidSignature(signature, for: data)

Вывод

Создавать безопасные и надежные приложения для iOS непросто. Однако вы можете без особых усилий повысить безопасность своих приложений для iOS, придерживаясь лучших практик. Защита пользовательских данных должна иметь высокий приоритет и никогда не должна игнорироваться.

Если вы хотите узнать больше о внутреннем устройстве системы безопасности Apple iOS, вам следует ознакомиться с руководством по безопасности iOS. Это техническое руководство, объясняющее, как данные защищены на iOS. Он обновляется с каждым выпуском iOS и рассказывает о новых технологиях и способах их защиты.

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