Securing user data with Keychain for iOS

08 August 2018

Introduction 

If you’ve ever built an iOS app, you’ve probably come across a situation where you need to store sensitive information on behalf of the user. For this, Apple’s Keychain Services is your guy; it’s good for saving secure data. UserDefaults are fine, when you’re dealing with preferences, but you should never store credentials in them. The keychain is not limited to passwords. You can store other secrets that the user explicitly cares about, such as credit card information or even short notes. You can also store items that the user needs but may not be aware of, for example, the cryptographic keys and certificates that you manage. You can use the keychain to store these items as well. Keep in mind that Keychain is stored in a secure enclave in the A7 or newest chips. There are many wrappers where you can easily use Keychain, but I want to show that Keychain is not difficult and we can use it as well.

Storing keys in the Keychain

Cryptographic keys are strings of bytes that you can combine with other data in specialized mathematical operations to enhance security. When you generate keys yourself, you can store them in keychain. To do this, begin by creating a query dictionary containing and describing the item. To make our codebase cleaner and more maintainable, let’s create WritableKeychain protocol with prepareKey method and using extension of this protocol write simple implementation storing keys in keychain like this:

Here’s what’s happening in the code:

  1. This attributes dictionary uses the kSecClassKey value for the kSecClass entry to indicate a key item. You can also apply an application tag that lets you distinguish the key from others when later searching for it. kSecValueData tell us that Keychain stores key as CFData object
  1. Avoid reusing tags in the first place. Before adding a key with a given tag and type (or whatever other distinguishing characteristic you have), read existing keys from the keychain with those same characteristics. If you find a potential duplicate, either reuse the old key, create the new key using a different tag, or delete the old key using the SecItemDelete(_:) function before adding a new one.
  1. Use the SecItemAdd(_:_:) function to actually store the item, if a problem occurs throw the error.

When you want to retrieve the key, you have to make another query using the same application tag. To handle this let’s make another protocol:

Let’s take a look at the code:

  1. The above dictionary indicates that the key should be of a cryptographic item and should have the tag used in that example and above. The last line says that the value should be returned in the form of a CFDataRef object.
  1. You use this dictionary with the SecItemCopyMatching(_:_:) function to execute a search and populate an empty reference that you supply. If the call is successful, as indicated by the status result, you can then use the returned key reference to carry out cryptographic operations
  1. Our key is returned as a Data object; let’s check it and convert it to String.

That’s it. Let’s make simple manager to create and retrieve keys for our purposes :)

Adding a Password to the Keychain

The Keychain Service lets us to enable simple, secure storage for users password. In this way you avoid repeatedly asking the user for a password, and you don’t have to implement your own encryption, which can be prone to errors. To get started, create a structure to hold user credentials:

Also identify the server that your app is working with:

Next we can extend our Writable protocol with an additional method;

and create an implementation to store those credentials in Keychain.

Let’s see what happened:

  1. The query dictionary’s first key-value pair indicates that the item is an Internet password. Indeed, the next two key-value pairs in the query provide this information, attaching the user name acquired from the user as the account, along with a domain name appropriate to this password as the server.

Note

Keychain service also provides us kSecClassGenericPassword item class. Generic passwords are similar to Internet passwords, but they lack certain attributes specific to remote access (for example don’t have to have kSecAttrServer attribute). If you don’t need extra attributes, user generic password instead.

  1. Sometimes your users can login to your app with different accounts on one device. Before saving new credentials to Keychain, please delete the previous entries.

Our user credentials are now saved in Keychain and protected; Great! :)

Let’s retrieve those values back;

Expand Readable protocol with new property and make implementation:

Some explanations:

  1. This query searches for Internet password items whose server attribute matches the server attribute you previously used when adding the password item. The query requests from the password item both its attributes and its data. The value of kSecReturnAttributes indicates that a dictionary of the (unencrypted) attributes of an item should be returned in the form of a CFDictionary object using the keys and values defined in Keychain Service. In out example kSecValueData to get password and kSecAttrAccount to get username. More info read Item Attribute Keys and Values
  1. After creating the query you call SecItemCopyMatching(_:_:) function to search your saved credentials.
  1. Because in your search you requested multiple return types, you should expect the result to be a dictionary. As mention above you recover username from kSecAttrAccount attribute and password from kSecValueData attribute.

That’s it. Let’s expand our previous manager with this additional method to create and retrieve user credentials.

Our method call

Summary

A good understanding of Keychain let us realize how implementing your wrapper can be pretty easy and beneficial to users and securing their credentials. You can avoid extra third-party libraries and speed up the build time as well as reduce app size. I’ll tell you more about security in iOS in upcoming blog posts.

Further reading

Kamil Makowski
Kamil Makowski