Как стать автором
Обновить

Основы безопасности: Keychain и Хеширование

Время на прочтение 10 мин
Количество просмотров 27K
Автор оригинала: Ryan Ackermann
Один из наиболее важных аспектов разработки программного обеспечения, который также считается одним из самых загадочных и страшных (поэтому избегается, как чума) — это безопасность приложений. Пользователи ожидают, что их приложения будут корректно работать, хранить их личную информацию и защищать эту информацию от потенциальных угроз.



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

Apple имеет несколько API, которые помогут защитить ваши приложения, и вы изучите их при работе с Keychain. Кроме того, вы будете использовать CryptoSwift — хорошо изучите и просмотрите библиотеку с открытым исходным кодом, которая реализует криптографические алгоритмы.

Начало


Используйте эту ссылку, чтобы загрузить проект для работы.

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

Как только вы распакуете архив, обязательно откройте файл Friendvatars.xcworkspace, чтобы подключить зависимости исользуйте CocoaPod. Скомпилируйте и запустите приложение. Вы увидите, что оно стартует с экрана авторизации в систему:

iOS Security

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

Почему важна безопасность


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

iOS Security

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

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

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

Keychain


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

Зачем использовать Keychain для более простых решений? Достаточно ли хранить пароль пользователя в base-64 в UserDefaults? Точно нет! Для злоумышленника вполне тривиально восстановить пароль, сохраненный таким образом. Безопасность сложна, и попытка создания вашего собственного решения не очень хорошая идея. Даже если ваше приложение не для финансового учреждения, хранение личных данных пользователя не следует воспринимать легкомысленно.

iOS Security

Прямое взаимодействие с Keychain является довольно не простым, особенно в Swift. Вы должны использовать Security фреймворки, которые в основном написана на языке C.

К счастью, вы можете избежать использования этих низкоуровневых API, заимствуя у Apple обертку для Swift из примера GenericKeychain. KeychainPasswordItem обеспечивает простой в использовании интерфейс для работы с Keychain и уже добавлен в стартовый проект.

Время погрузиться в код!

Использование Keychain


Откройте AuthViewController.swift. Этот контроллер отвечает за форму для авторизации, которую вы видели вначале. Если вы перейдете к разделу Actions, вы заметите, что метод signInButtonPressed ничего не делает. Пришло время это исправить.

Добавьте следующий код в раздел Helpers в самом низу:

private func signIn() {
  // 1
  view.endEditing(true)
  
  // 2
  guard let email = emailField.text, email.count > 0 else {
    return
  }
  guard let password = passwordField.text, password.count > 0 else {
    return
  }
  
  // 3
  let name = UIDevice.current.name
  let user = User(name: name, email: email)
}

Вот как это происходит:

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

Примечание: Вы можете изменить имя своего Mac (которое используется sim-картой), перейдя в System Preferences -> Sharing. Кроме того, вы можете изменить имя вашего iPhone, перейдя в Settings -> General -> About -> Name.

Теперь добавьте следующее в метод signInButtonPressed:

signIn()

Этот код вызывает метод signIn, когда выполняется signInButtonPressed.

Найдите textFieldShouldReturn и замените TextFieldTag.password.rawValue в break под case на следующее:

signIn()

Теперь метод signIn() будет вызван, когда пользователь нажмет Return на клавиатуре, после того как он ввел текст в поле пароля, в то время как поле пароля имеет фокус и уже содержит текст.

Метод signIn() еще не реализован до конца. Нам все еще нужно сохранить объекты user и password. Все это будет реализовано во вспомогательном классе.

Откройте AuthController.swift, который является статическим классом — он будет содержать бизнес логику, связанную с аутентификацией.

Для начала, в самом верху файла над isSignedIn следует добавить следующее:

static let serviceName = "FriendvatarsService"

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

class func signIn(_ user: User, password: String) throws {
  try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(password)
  
  Settings.currentUser = user
}

Этот метод надежно сохранит информацию для авторизаций пользователя в Keychain. Он создает KeychainPasswordItem с названием сервиса, который вы определили вместе с уникальным идентификатором (account).

Для этого приложения, электронная почта пользователя используется в качестве идентификатора для Keychain, но другие данные также могут служить идентификатором или уникальным именем пользователя. Наконец, в Settings.currentUser присваивается значение user — все это сохраняется в UserDefaults.

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

В верхней части AuthController.swift сразу после import Foundation добавьте следующее

import CryptoSwift

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

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

Затем добавьте следующий код над signIn:

class func passwordHash(from email: String, password: String) -> String {
  let salt = "x4vV8bGgqqmQwgCoyXFQj+(o.nUNQhVP7ND"
  return "\(password).\(email).\(salt)".sha256()
}

Этот метод принимает адрес электронной почты и пароль, и возвращает хешированную строку. Константа salt это уникальная строка, которая делает из обычного пароля редкий .sha256() — это метод c фреймворка CryptoSwift, который хеширует введенную строку по алгоритму SHA-2.

В предыдущем примере злоумышленник, который взломал Keychain, найдет этот хеш. Злоумышленник может создать таблицу часто используемых паролей и их хэшей для сравнения с этим хэшем. Если вы хэшировали только введенные данные пользователя без salt, и пароль существовал в хэш-таблице злоумышленников, пароль может быть взломан.

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

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

Вернитесь назад к методу signIn(_:password:), замените строку кода, которая вызывает метод savePassword на следующее:

let finalHash = passwordHash(from: user.email, password: password)
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(finalHash)

signIn теперь хранит сильный хэш, а не “сырой” пароль. Теперь пришло время добавить его в контроллер представления.

Вернитесь к AuthViewController.swift и добавьте в конец метода signIn() этот код:

do {
  try AuthController.signIn(user, password: password)
} catch {
  print("Error signing in: \(error.localizedDescription)")
}

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

Возможно, вы заметили, что AuthController.swift имеет статическую переменную с именем isSignedIn. В настоящее время она всегда возвращает false, даже если пользователь зарегистрирован.

В AuthController.swift измените isSignedIn:

static var isSignedIn: Bool {
  // 1
  guard let currentUser = Settings.currentUser else {
    return false
  }
  
  do {
    // 2
    let password = try KeychainPasswordItem(service: serviceName, account: currentUser.email).readPassword()
    return password.count > 0
  } catch {
    return false
  }
}

Вот что тут происходит:

  1. Вы сразу же проверяете текущего пользователя, хранящегося в UserDefaults. Если пользователь не существует, то идентификатор для поиска хеша пароля в Keychain также будет отсутствовать, поэтому вы указываете, что он не зарегистрирован в системе.
  2. Вы получаете хеш-пароль из Keychain, и если пароль существует и не пуст, пользователь считается зарегистрированным.

Теперь handleAuthState в AppController.swift будет работать корректно, но после входа в систему потребуется перезапустить приложение, чтобы корректно обновить UI. Но есть хороший способ уведомлять приложение об изменении состояния, например, аутентификация пользователя, — используя notification.

Добавьте следующее в конец AuthController.swift:

extension Notification.Name {
  static let loginStatusChanged = Notification.Name("com.razeware.auth.changed")
}

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

Чтобы использовать данное имя пользовательского уведомления, добавьте следующее к нижней части метода signIn (_: password :):

NotificationCenter.default.post(name: .loginStatusChanged, object: nil)

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

Внутри AppController.swift добавьте метод init над show(in:):

init() {
  NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleAuthState),
    name: .loginStatusChanged,
    object: nil
  )
}

Данный код зарегистрирует AppController в качестве наблюдателя вашего регистрационного имени. При срабатывании он вызывает callAuthState.

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


Вы заметите, что нет никаких аватаров, а просто имена друзей. На это не очень приятно смотреть. Вероятно, вы должны выйти из этого незавершенного приложения и забыть о нем. О да, даже кнопка выхода не работает. Время поставить 1-звездочку в качестве оценки и действительно отдать это приложение его разработчику обратно!


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

Вернитесь назад к AuthViewController.swift и добавьте следующее под signIn(_:password:):

class func signOut() throws {
  // 1
  guard let currentUser = Settings.currentUser else {
    return
  }
  
  // 2
  try KeychainPasswordItem(service: serviceName, account: currentUser.email).deleteItem()
  
  // 3
  Settings.currentUser = nil
  NotificationCenter.default.post(name: .loginStatusChanged, object: nil)
}

Это довольно просто:

  1. Вы проверяете, сохранили ли вы текущего пользователя или нет, если вы этого не сделали ранее.
  2. Вы удаляете хэш-пароль из Keychain.
  3. Вы очищаете объект пользователя и публикуете уведомление.

Чтобы подключить это, перейдите к FriendsViewController.swift и добавьте следующее в signOut:

try? AuthController.signOut()

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

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

Скомпилируйте и запустите приложение, затем нажмите кнопку «Выйти».



Теперь у вас есть полный работающий пример аутентификации в приложении!

Хеширование


Вы отлично справились с созданием аутентификации! Однако веселье еще не закончилось. Теперь вы преобразуете это пустое пространство перед именами в списке друзей.

В FriendsViewController.swift отображается список объектов модели User. Вы также хотите отображать изображения аватаров для каждого пользователя в представлении. Поскольку есть только два атрибута для User, имя и адрес электронной почты, как вы собираетесь показывать изображение?

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

У каждого из этих пользователей уже есть аватар, связанный с их электронной почтой. Так что единственное, что вам нужно сделать, это выполнить запрос к Gravatar и получить изображения по запрошенным пользователям. Для этого вы создадите MD5 хеш их электронной почты, чтобы создать URL запросы.

Если вы посмотрите на документацию на сайте Gravatar, вы увидите, что для создания запроса нужен хешированный адрес электронной почты. Это будет кусок пирога, так как вы можете использовать CryptoSwift. Добавьте вместо комментариев Gravatar в tableView (_: cellForRowAt :) следующее:

// 1
let emailHash = user.email.trimmingCharacters(in: .whitespacesAndNewlines)
                          .lowercased()
                          .md5()

// 2
if let url = URL(string: "https://www.gravatar.com/avatar/" + emailHash) {
  URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data, let image = UIImage(data: data) else {
      return
    }
    
    // 3
    self.imageCache.setObject(image, forKey: user.email as NSString)
    
    DispatchQueue.main.async {
      // 4
      self.tableView.reloadRows(at: [indexPath], with: .automatic)
    }
  }.resume()
}

Давайте разберем:

  1. Сначала вы форматируете адрес электронной почты в соответствии с документацией Gravatar, а затем создаете хеш MD5.
  2. Вы создаете URL-адрес Gravatar и URLSession. Вы загружаете UIImage из возвращаемых данных.
  3. Вы кешируете изображение, чтобы избежать повторных выборок для адреса электронной почты.
  4. Вы перезагружаете строку в представлении таблицы, чтобы отображалось изображение аватара.

Скомпилируйте и запустите приложение. Теперь вы можете видеть изображения и имена ваших друзей:


Примечание: Если ваша электронная почта возвращает дефолтное изображение (белый цвет на синем G), перейдите на сайт Gravatar и загрузите свой собственный аватар и присоединитесь к своим друзьям!

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

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

В конце, обязательно изучите дополнительные алгоритмы безопасности, предоставляемые CryptoSwift.

Надеюсь, Вам понравилась эта статья! Если у вас есть какие-либо вопросы или комментарии, присоединяйтесь обсуждению!
Теги:
Хабы:
+7
Комментарии 0
Комментарии Комментировать

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн