One of the most important aspects of software development also happens to be considered one of the most mysterious and scary (and is thus avoided like the plague): application security. Users expect their applications to run correctly, keep their information private, and protect that information from potential threats.
In this tutorial, you will dive into the basics of iOS security. You’ll work with some basic cryptographic hashing methods to securely store user input in the iOS keychain – keeping your users’ data private and protected.
Apple has several APIs that will help keep your applications secure, and you’ll explore these while working with the keychain. In addition, you’ll use CryptoSwift – a well-reviewed, open-source library that implements cryptographic algorithms.
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the starter project.
The sample app allows users to log in and see photos of their friends. Most of the app is already connected for you; your job is to secure the app.
Once you unzip the download materials, be sure to open Friendvatars.xcworkspace to properly include all the CocoaPod dependencies. Build and run to see that the app opens to a login screen:
Currently, nothing happens when you tap Sign In. This is because there isn’t a way to save the user’s credentials. That’s what you’re going to add first.
Why Security is Important
Before you dive into the code, you should understand why security in your application is necessary. The security of your application is especially critical if you’re storing private user data such as emails, passwords, or bank account information.
Why does Apple take security so seriously? From the photos you take, to the number of steps that were achieved during the day, your iPhone stores a lot of personal data. Keeping this data safe is very important.
Who are the attackers in the iOS ecosystem, and what do they want? An attacker might be a criminal, a business competitor, even a friend or relative. Not all attackers want the same thing. Some might want to cause damage or corrupt information, while others might want to see what presents they are getting for their birthdays.
It’s your job to make sure that the data being held by your application is protected against potential threats. Fortunately, Apple has built many strong APIs that simplify this task.
Apple’s Keychain
One of the most important security elements for Apple developers is the iOS Keychain, which is a specialized database for storing metadata and sensitive information. Using Keychain is the best practice for storing small pieces of data that are critical to your app such as secrets and passwords.
Why use the Keychain over simpler solutions? Wouldn’t storing the base-64 encoding the user’s password in UserDefaults
be enough? Definitely not! It’s trivial for an attacker to recover a password stored that way. Security is difficult, and attempting your own custom solution is not a good idea. Even if your app is not for a financial institution, storing private user input should not be taken lightly.
Interacting with the Keychain directly is complicated, especially in Swift. You have to use the Security framework that is mostly written in C.
Fortunately, you can avoid using these low level APIs by borrowing a Swift wrapper from Apple’s sample code GenericKeychain. KeychainPasswordItem provides an easy-to-use Swift interface to the Keychain and is already included in the starter project.
Time to dive into code!
Using the Keychain
Open AuthViewController.swift. This view controller is responsible for the login form you saw initially. If you scroll down to the Actions section, you’ll notice that signInButtonPressed
isn’t doing anything. Time to change that. Add the following 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 what is going on:
- You dismiss the keyboard to confirm that the user’s action did something.
- You take the email and password the user input. If either is zero length, then you don’t want to continue. In a real world application, you should show the user an error here as well.
- You assign a name to the user which, for purposes of this tutorial, you take from the device name.
Now add the following in signInButtonPressed
:
signIn()
This calls your signIn
method when signInButtonPressed
is triggered.
Find textFieldShouldReturn
and replace the break
under case TextFieldTag.password.rawValue
with:
signIn()
Now signIn()
is called when the user taps return on the keyboard while the password field has focus and contains text.
signIn()
is not complete yet. You still need to store the user object as well as the password. You’ll implement this in a helper class.
Open AuthController.swift, which is a static class that will hold the logic related to authentication for this app.
First, add the following at the top of the file above isSignedIn
:
static let serviceName = "FriendvatarsService"
This defines the service name that will be used to identify the app’s data in the Keychain. To use this constant, create a signIn
method at the end of the class like so:
class func signIn(_ user: User, password: String) throws {
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(password)
Settings.currentUser = user
}
This method stores the user’s login information securely in the Keychain. It creates a KeychainPasswordItem
with the service name you defined along with a unique identifier (account
).
For this application, the user’s email is used as the identifier for the Keychain, but other examples could be a user ID or username that is unique. Finally, Settings.currentUser
is set with user
– this is stored in UserDefaults
.
This method should not be considered complete! Storing the user’s password directly isn’t the best practice. For example, if an attacker compromised Apple’s Keychain, he could read your user’s passwords in plain text. A better solution is to store a hash built from the user’s identity.
At the top of AuthController.swift add the following below the Foundation
import:
import CryptoSwift
CryptoSwift is one of the most popular collections of many standard cryptographic algorithms written in Swift. Cryptography is difficult and needs to be done correctly to be useful. Using a popular library for security means you don’t have to be responsible for the implementation of standardized hashing functions. The best cryptography is open to the public for review.
Next add the following 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 and password, and returns a hashed string. The salt is a unique string used to make common passwords, well, uncommon. sha256()
is a CryptoSwift method that completes a type of SHA-2 hash on your input string.
In the example from earlier, an attacker who compromised Keychain would find this hash. The attacker might create a table of commonly used passwords and their hashes to compare against this hash. If you hashed just the user’s input without salting, and the password existed in the attackers hash table, the password would be compromised.
Incorporating a salt increases the complexity of the attack. Furthermore, you combine the user’s email and password with the salt to create a hash that won’t be easily cracked.
Back in signIn(_:password:)
, replace the line that calls savePassword
with this:
let finalHash = passwordHash(from: user.email, password: password)
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(finalHash)
signIn
now stores a strong hash, rather than a raw password. Now it’s time to add this to the view controller.
Head back to AuthViewController.swift and add the following to the bottom of signIn()
:
do {
try AuthController.signIn(user, password: password)
} catch {
print("Error signing in: \(error.localizedDescription)")
}
Although this will store the user and save a hashed password, it’ll take a little more for the app to be signed in. AppController.swift needs a way to be notified when authentication changes.
You may have noticed that AuthController.swift has a static variable named isSignedIn
. Currently, it’s always returning false
even if the user signs in.
In AuthController.swift, update isSignedIn
to:
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’s going on:
- Right away, you check the current user stored in
UserDefaults
. If no user exists, there won’t be an identifier to lookup the password hash from the Keychain, so you indicate they are not signed in. - You read the password hash from the Keychain, and if a password exists and isn’t blank, the user is considered logged in.
Now handleAuthState
in AppController.swift will work correctly, but it would take a fresh app launch after signing in to update the UI correctly. Instead, a good way to notify the app of a state change such as authentication is through notifications.
Add the following to the bottom of AuthController.swift:
extension Notification.Name {
static let loginStatusChanged = Notification.Name("com.razeware.auth.changed")
}
It’s good practice to use a reverse domain identifier when composing custom notifications, which is usually derived from the app’s bundle identifier. Using a unique identifier can help when debugging so anything related to your notification stands out from other frameworks mentioned in your logs.
To use this custom notification name, add the following to the bottom of signIn(_:password:)
:
NotificationCenter.default.post(name: .loginStatusChanged, object: nil)
This will post a notification that can be observed by other parts of the application.
Inside of AppController.swift, add an init
method above show(in:)
:
init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAuthState),
name: .loginStatusChanged,
object: nil
)
}
This will register AppController as an observer of your login notification. It will call handleAuthState
when triggered.
Build and run. After signing in using any email and password combination, you’ll see the a list of friends:
You’ll notice that there aren’t any avatars, just names of friends. That’s not very pleasing to look at. You should probably sign out and forget about this unfinished app. Oh come on, even the sign out button doesn’t work. Time to leave a 1 star review and really give it to the developer!
Signing in works great, but there isn’t a way to sign out of the app. This is actually pretty easy to achieve, since there’s a notification that will signal any authentication state change.
Head back to AuthController.swift and add the following method below 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)
}
This one is fairly simple but here’s the breakdown:
- You check if you’ve stored a current user, and bail out early if you haven’t.
- You delete the password hash from the Keychain.
- You clear the user object and post the notification.
To wire this up, jump over to FriendsViewController.swift and add the following to the currently empty signOut
:
try? AuthController.signOut()
Your new method is called to clear out the signed in user’s data when the Sign Out button is selected.
It’s a good idea to handle errors in your applications, but for the sake of this tutorial you’ll just ignore any error.
Build and run, then tap the Sign Out button.
Now you have a complete example of authentication working in an app!
Hashing
You did a great job getting authentication set up! However the fun isn’t over yet. Now you’ll address that blank space in front of the names in the friends view.
In FriendsViewController.swift, there is a list of User
model objects displayed. You also want to show avatar images for each user in the view. Since there are only two attributes on the User
, a name and email, how are you supposed to show an image?
It turns out there is a service that takes an email address and associates it with an avatar image: Gravatar! If you haven’t heard of Gravatar before, it’s commonly used on blogs and forums to globally associate an email address with an avatar. This simplifies things so that users don’t have to upload a new avatar to every forum or site they join.
Each of these users has an avatar associated with their email already. So the only thing you have to do is make a request to Gravatar and get their images. To do so, you’ll create a MD5 hash of their email to build the request URL.
If you look at the docs on Gravatar’s site, you’ll see you need a hashed email address to build a request. This will be a piece of cake since you can leverage CryptoSwift
. Add the following, in place of the comment about Gravatar, 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()
}
Here’s the breakdown:
- First you normalize the email according to Gravatar’s docs, then you create a MD5 hash.
- You construct the Gravatar URL and URLSession. You load a
UIImage
from the returned data. - You cache the image to avoid repeat fetches for an email address.
- You reload the row in the table view so the avatar image shows up.
Build and run. Now you can view your friends’ avatar images and names:
Where to Go From Here?
You now have a complete app the handles basic iOS security and authentication, and you can view avatars powered by Gravatar. You learned about the importance of security, about the iOS keychain and some best practices like storing hashes instead of plain text values. Hopefully, you also had a great time learning about this!
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.
If you’re interested in more ways to secure your applications, learn to use biometric sensors on the latest Apple products in this tutorial.
You can also read more about Apple’s Security framework if you want to really dig into the framework.
Finally, be sure to explore more security algorithms provided by CryptoSwift.
I hope you enjoyed this tutorial! If you have any questions or comments, please join the discussion below!
The post Basic iOS Security: Keychain and Hashing appeared first on Ray Wenderlich.