Security Essentials: Keychain and Hashing

Original author: Ryan Ackermann
  • Transfer
  • Tutorial
One of the most important aspects of software development, which is also considered one of the most mysterious and scary (therefore avoided like a plague), is application security. Users expect their applications to work correctly, store their personal information and protect this information from potential threats.



In this article, you will dive into the basics of security in iOS. You will work with some basic cryptographic hashing methods to securely store received data in Keychain - saving and protecting user data in the application.

Apple has several APIs that will help protect your applications, and you will learn them when working with Keychain. In addition, you will use CryptoSwift - study and browse the open source library that implements cryptographic algorithms well.

Start


Use this link to download the project for work.

We will be working on an application that allows users to register and see photos of their friends. Most of the application has already been implemented, your task is to protect this application.

Once you unzip the archive, be sure to open the Friendvatars.xcworkspace file to use dependencies using CocoaPod. Compile and run the application. You will see that it starts from the login screen to the system:

iOS Security

Currently, when you click on the Sign in button, nothing happens. This is due to the fact that the application does not implement a way to save user credentials. This is what you will need to add first.

Why security matters


Before you dive into working with code, you must understand why security is necessary in your application. The security of your application is especially important if you store personal user data, such as emails, passwords or bank account information.

iOS Security

Why is Apple taking security so seriously? The photos you take, up to the number of steps that were taken during the day, your iPhone stores a lot of personal data. Protecting this data is very important.

Who are the attackers in the iOS ecosystem and what do they want? An attacker can be a criminal, a business competitor, even a friend or relative. Not all attackers want the same thing. Some may want to harm or ruin the information, while others may want to know what gifts they will receive on their birthdays.

Your task is to make sure that the data stored in your application is protected from possible threats. Fortunately, Apple has developed many powerful APIs that simplify this task.

Keychain


One of the most important security features for iOS developers is Keychain , a specialized database for storing metadata and confidential information. Using Keychain is best practice for storing small pieces of data that are critical to your application, such as secrets and passwords.

Why use Keychain for simpler solutions? Is it enough to store the user password in base-64 in UserDefaults? Absolutely not! It is trivial for an attacker to recover a password saved in this way. Security is complex, and trying to create your own solution is not a good idea. Even if your application is not for a financial institution, the storage of the user's personal data should not be taken lightly.

iOS Security

Direct interaction with Keychain is rather difficult, especially in Swift. You should use Security frameworks , which are mostly written in C.

Fortunately, you can avoid using these low-level APIs by borrowing the Apple wrapper for Swift from the GenericKeychain example . KeychainPasswordItem provides an easy-to-use interface for working with Keychain and is already added to the start-up project.

Time to dive into the code!

Using Keychain


Open AuthViewController.swift . This controller is responsible for the authorization form that you saw at the beginning. If you go to the Actions section , you will notice that the signInButtonPressed method does nothing. It's time to fix it.

Add the following code to the bottom of the Helpers section :

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)
}

Here's how it goes:

  1. You remove the keyboard to confirm that the user action has been completed.
  2. You accept the email address and password of the user. If the length of the entered information in the field is zero, then the execution of the function will not continue. In a real application, you should show the user an error.
  3. You assign a username, which you take from the device name for training purposes in this article.

Note: You can change the name of your Mac (which is used by the sim card) by going to System Preferences -> Sharing . Alternatively, you can change the name of your iPhone by going to Settings -> General -> About -> Name .

Now add the following to the signInButtonPressed method :

signIn()

This code calls the signIn method when signInButtonPressed is executed.

Find textFieldShouldReturn and replace TextFieldTag.password.rawValue in break under case with the following:

signIn()

Now, the signIn () method will be called when the user presses Return on the keyboard after he has entered the text in the password field, while the password field has focus and already contains the text.

The signIn () method has not yet been fully implemented. We still need to save the user and password objects. All this will be implemented in a helper class.

Open AuthController.swift , which is a static class - it will contain the business logic associated with authentication.

To get started, at the very top of the file above isSignedIn , add the following:

static let serviceName = "FriendvatarsService"

This code defines the name of the service that will be used to identify application data in Keychain. To use this constant, create the signIn method at the end of the class:

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

This method will securely store information for user authorizations in Keychain. It creates a KeychainPasswordItem with the name of the service that you defined with a unique identifier (account).

For this application, the user's email is used as an identifier for Keychain, but other data can also serve as an identifier or a unique username. Finally, a value of user is assigned in Settings.currentUser - all this is stored in UserDefaults .

This method should not be considered complete! Storing a user's password directly is not a good practice. For example, if an attacker hacked Keychain, he could get the passwords of each user in plain text. The best solution is to store a password hash built on the basis of user identification.

At the top of AuthController.swift, immediately after import Foundation, add the following

import CryptoSwift

CryptoSwift is one of the most popular collections of many standard cryptographic algorithms written in Swift. Cryptography is complex and must be done correctly so that it truly benefits. Using the popular library for security means that you are not responsible for implementing standardized hashing functions. The best cryptography techniques are open to the public.

Note: Apple’s CommonCrypto framework provides many useful hashing functions, but working with them is not easy in Swift. That's why we chose the CryptoSwift library for this article.

Then add the following code above signIn :

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

This method takes an email address and password, and returns a hashed string. The salt constant is a unique string that makes a rare .sha256 () from a regular password - this is a method from the CryptoSwift framework that hashes the entered string using the SHA-2 algorithm .

In the previous example, the attacker who hacked Keychain will find this hash. An attacker can create a table of commonly used passwords and their hashes to compare with this hash. If you only hashed user input without salt and the password existed in the hash table of the attackers, the password could be cracked.

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

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

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

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

signIn now stores a strong hash, not a raw password. Now it's time to add it to the view controller.

Go back to AuthViewController.swift and add this code to the end of the signIn () method :

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

Despite the fact that this code saves the user and saves a hashed password, the application will need something else in order to log in. AppController.swift should be notified when authentication changes.

You may have noticed that AuthController.swift has a static variable called isSignedIn . Currently, it always returns false, even if the user is registered.

In AuthController.swift, change 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
  }
}

Here's what happens here:

  1. You immediately check the current user stored in UserDefaults . If the user does not exist, then the identifier for searching the password hash in Keychain will also be absent, therefore you indicate that it is not registered in the system.
  2. You get a hash password from Keychain , and if the password exists and is not empty, the user is considered registered.

Now handleAuthState in AppController.swift will work correctly, but after logging in you will need to restart the application in order to correctly update the UI . But there is a good way to notify the application of a state change, for example, user authentication, using notification.

Add the following to the end of AuthController.swift :

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

It’s good practice to use a domain identifier when composing custom notifications, which is usually taken from the bundle identifier of an application. Using a unique identifier can help with debugging the application, so everything related to your notification is distinguished from other frameworks mentioned in your logs.

To use this custom notification name, add the following to the bottom of the signIn (_: password :) method :

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

This code will send a notification that may be detected by other parts of the application.

Inside AppController.swift, add the init method above show (in :) :

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

This code will register AppController as an observer of your login name. When triggered, it calls callAuthState .

Compile and run the application. After logging in to the system, using any combination of email and password, you will see a list of friends:


You will notice that there are no avatars, just names of friends. This is not very pleasant to watch. You should probably exit this unfinished application and forget about it. Oh yes, even the exit button does not work. Time to put a 1-star as an assessment and really give this application back to its developer!


Logging works fine, but there is no way to exit the application. This is actually quite easy to achieve, since there is a notification that will signal an authentication status change.

Go back to AuthViewController.swift and add the following under 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)
}

It is pretty simple:

  1. You check whether you saved the current user or not, if you have not done so previously.
  2. You remove the hash password from Keychain.
  3. You clear the user object and post a notification.

To connect this, go to FriendsViewController.swift and add the following to signOut:

try? AuthController.signOut()

Your new method is called to clear the data of the user who is logged in when the "Logout" button is pressed.

Dealing with errors in your application is a good idea, but in view of this tutorial, ignore any errors.

Compile and run the application, then click the "Exit" button.



Now you have a full working example of authentication in the application!

Hashing


You did a great job of creating authentication! However, the fun is not over yet. You will now convert this blank space before the names in your friends list.

In FriendsViewController.swift displays a list of objects of the User model. You also want to display avatar images for each user in the view. Since there are only two attributes for User, the name and email address, how are you going to display the image?

Turns out there is a service that takes an email address and associates it with an avatar image: Gravatar! If you have not heard of Gravatar, it is commonly used in blogs and forums to globally associate an email address with an avatar. It simplifies the work, so users do not need to upload a new avatar to each forum or site to which they join.

Each of these users already has an avatar associated with their email. So the only thing you need to do is complete the request to Gravatar and get the images for the requested users. To do this, you will create an MD5 hash of their email to create URL requests.

If you look at the documentation on the Gravatar website, you will see that a hashed email address is required to create the request. This will be a piece of cake, as you can use CryptoSwift. Add the following instead of Gravatar comments in 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()
}

Let's look at:

  1. First, you format the email address according to the Gravatar documentation, and then create the MD5 hash .
  2. You are creating a Gravatar URL and URLSession . You load UIImage from the returned data.
  3. You cache the image to avoid re-fetching for the email address.
  4. You reload the row in the table view so that the avatar image is displayed.

Compile and run the application. Now you can see the images and names of your friends:


Note: If your email returns the default image (white on blue G), go to the Gravatar website and upload your own avatar and join your friends!

If you're interested in other ways to protect your applications, check out the use of biometric sensors in the latest Apple products in this article .

You can also learn more about Apple's security infrastructure and if you really want to take a peek into the framework.

In the end, be sure to check out the additional security algorithms provided by CryptoSwift.

I hope you enjoyed this article! If you have any questions or comments, join the discussion!

Also popular now: