Basic iOS Security: Keychain and Hashing

综合技术 Ray Wenderlich (源链)

Update note:
This tutorial has been updated for Xcode 9.2, Swift 4, iOS 11 and the iPhone X by Ryan Ackermann. The original tutorial was written by Chris Lowe.

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

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

Here’s what is going on:

  1. You dismiss the keyboard to confirm that the user’s action did something.
  2. 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.
  3. You assign a name to the user which, for purposes of this tutorial, you take from the device name.

You can change the name of your Mac (used by the sim) by going to System Preferences ▸ Sharing and changing the computer’s name at the top. Additionally you can change your iPhone’s name by going to Settings ▸ General ▸ About ▸ Name.

Now add the following in signInButtonPressed


This calls your signIn
method when signInButtonPressed
is triggered.

Find textFieldShouldReturn
and replace the break
under case TextFieldTag.password.rawValue


Now signIn()
is called when the user taps return
on the keyboard while the password field has focus and contains text.

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:
  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 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.

: Apple’s CommonCrypto framework provides many useful hashing functions for you, but it’s not easy to interact with it in Swift. This is why for this tutorial we opted for the CryptoSwift library.

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.

For authentication with a server backend, the app and server would share the same salt. This allows them to build hashes in the same way and compare the two hashes to verify identity.

Back in signIn(_:password:)
, replace the line that calls savePassword
with this:

let finalHash = passwordHash(from:, password: password)
try KeychainPasswordItem(service: serviceName, account:

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

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

Here’s what’s going on:

  1. 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.
  2. 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:)
: .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() {
    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 {
  // 2
  try KeychainPasswordItem(service: serviceName, account:
  // 3
  Settings.currentUser = nil .loginStatusChanged, object: nil)

This one is fairly simple but here’s the breakdown:

  1. You check if you’ve stored a current user, and bail out early if you haven’t.
  2. You delete the password hash from the Keychain
  3. 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

Now you have a complete example of authentication working in an app!


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 = .whitespacesAndNewlines)

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

Here’s the breakdown:

  1. First you normalize the email according to Gravatar’s docs, then you create a MD5
  2. You construct the Gravatar URL and URLSession. You load a UIImage
    from the returned data.
  3. You cache the image to avoid repeat fetches for an email address.
  4. 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:

If your email returns the default white on blue G, then head over to Gravatar’s site and upload your own avatar and join your friends!

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 thistutorial.

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!

Download Materials


用代码探究 KVO 原理(真原创) 我在上一篇文章 从使用 KVO 监听 readonly 属性说起 中谈了使用 KVO 一些问题。里面谈到了 KVO 的原理,我在网上搜了一下 KVO 原理,发现大家说的都是一样的。但是我在实际使用中发现事实并不是网上说的那样。所以打算用代码的形式来一步一步探究 KVO 的原理。 ...
iOS底层原理总结 – 探寻OC对象的本质... 对小码哥底层班视频学习的总结与记录。面试题部分,通过对面试题的分析探索问题的本质内容。 面试题:一个NSObject对象占用多少内存? 探寻OC对象的本质,我们平时编写的Objective-C代码,底层实现其实都是CC++代码。 OC的对象结构都是通过基础CC++的结...
2018 美团春招 iOS面试分享(一面) 2018 美团春招 iOS面试分享(一面) 说来惭愧,实习准备开始的太晚了。我是从3.5号开始准备的(各种原因,导师可能不放,论文第二个点没做完之类的),大致刷的是OC各种基础,牛客上面剑指offer一半的题目,常用排序和数据结构。昨晚接到的开发电话,说约个面试吧。当时心里一惊,因为没有HR通知...
自主可控、安全可信:SyberOS3.0让战场更安全更智能... 【通信产业网讯】(记者 施二) 9月21日,在“第三届军民融合发展高技术装备成果展”上,中国(中关村)智能终端操作系统产业联盟举办军民融合专业委员会成立仪式, 同期,中国(中关村)智能终端操作系统产业联盟理事单位 元心科技发布了SyberOS3.0版本。 “合创拓赢”: 军民融合专业委...
iOS监控-保护你的crash 原文地址 如何去衡量一款应用的质量好坏?为了回答这一问题, APM 这一目的性极强的工具向开发顺应而生。最早的 APM 开发只关注于 crash 、 cpu 这类的硬性指标。而随着移动开发市场的成熟,越来越多的数据指标也被加入到了 APM 的采集范畴中,包括感官体验相关的数据和...
Ray Wenderlich责编内容来自:Ray Wenderlich (源链) | 更多关于

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » Basic iOS Security: Keychain and Hashing

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录