One of the most important security elements for Apple developers is the iOS Keychain, a specialized database for storing metadata and sensitive information. Using Keychain is the best way to store small pieces of data that are critical to your app, like secrets and passwords.
It’s complicated to interact with Keychain directly, especially in Swift. You have to use the Security framework, which is written mostly in C.
There are different Swift wrappers that allow you to interact with Keychain. Apple even offers one called GenericKeychain to make your life easier.
Although you can easily use third-party wrappers to interface with the unfriendly API that Apple provides, understanding Keychain Services adds a valuable tool to your developer tool belt.
In this tutorial, you’ll delve deep into Keychain Services API and learn how to create your own wrapper, developing it as an iOS framework.
In particular, you’ll learn how to add, modify, delete and search for both generic and internet passwords. Furthermore, you’ll provide unit tests to verify that your code works as you expect.
For this tutorial, you’ll use SecureStore, a boilerplate iOS framework where you’ll implement your Keychain Services API.
Start by downloading the starter project using the Download Materials button at the top or bottom of this tutorial. Once you’ve downloaded it, open SecureStore.xcodeproj in Xcode.
To keep you focused, the starter project has everything related to implementing your wrapper already set up for you.
The structure of your project should look like this:
The code of your wrapper lives in the SecureStore group folder:
SecureStoreError.swift: Contains an
enum, which represents all the possible errors your wrapper can deal with. Conforming to
SecureStoreErrorprovides localized messages describing the error and why it occurred.
SecureStoreQueryable.swift: Defines a protocol with the same name as the file.
SecureStoreQueryableforces the implementer to provide a
queryproperty defined as a dictionary typed as
[String: Any]. Internally, your API only deals with those types of objects. More on that later.
SecureStore.swift: Defines the wrapper you’ll implement in this tutorial. It provides an initializer and a bunch of stubbed methods for adding, updating, deleting and retrieving your passwords from the Keychain. A consumer can create a wrapper’s instance by injecting some type that conforms to
- InternetProtocol.swift: Represents all the possible internet protocol values you can deal with.
- InternetAuthenticationType.swift: Describes the authentication mechanisms that your wrapper provides.
Along with the framework code, you should have two other folders: SecureStoreTests and TestHost. The former contains the unit tests you’ll ship with your framework. The latter contains an empty app, which you’ll use to test your framework API.
Before diving directly into the code, take a look at some theory!
An Overview of Keychain Services
Why use the Keychain over simpler solutions? Wouldn’t storing the user’s base-64 encoded password in
UserDefaults be enough?
Definitely not! It’s trivial for an attacker to recover a password stored that way.
Keychain Services help you to securely store items, or small chunks of data, into an encrypted database on behalf of the user.
Keychain Services operate differently depending on the operating system you’re running.
In iOS, apps have access to a single Keychain which includes the iCloud Keychain. Locking and unlocking the device automatically locks and unlocks Keychain. This prevents unwanted accesses. Furthermore, an app is only able to access its own items or those shared with a group to which it belongs.
On the other hand, macOS supports multiple keychains. You typically rely on the user to manage these with the Keychain Access app and work implicitly with the default keychain. Additionally, you can manipulate keychains directly; for example, creating and managing a keychain that is strictly private to your app.
When you want to store a secret such as a password, you package it as a keychain item. This is an opaque type which consists of two parts: data and a set of attributes. Just before it inserts a new item, Keychain Services encrypts the data then wraps it together with its attributes.
Use attributes to identify and store metadata or to control access to your stored items. Specify attributes as the keys and values of a dictionary expressed as a
CFDictionary. You can find a list of the available keys at Item Attribute Keys and Values. The corresponding values can be strings, numbers, some other basic types, or constants packaged with the Security framework.
Keychain Services provide special kinds of attributes that allow you to identify the class for a specific item. In this tutorial, you’ll use both
kSecClassInternetPassword to deal with generic and internet passwords.
Each class supports only a special set of attributes. In other words, not all attributes apply to a specific item class. You can verify them in the relevant item class value documentation.
Diving Into Keychain Services API
Since the code hides items from ill-intentioned users, Keychain Services provide a set of C functions to interact with. Here are the APIs you’ll use to manipulate both generic and internet passwords:
- SecItemAdd(_:_:): Use this function to add one or more items to a keychain.
- SecItemCopyMatching(_:_:): This function returns one or more keychain items that match a search query. Additionally, it can copy attributes of specific keychain items.
- SecItemUpdate(_:_:): This function allows you to modify items that match a search query.
- SecItemDelete(_:): This function removes items that match a search query.
While the functions above operate with different parameters, they all return a result code expressed as an
OSStatus. This is a 32-bit signed integer which can assume one of the values listed in Item Return Result Keys.
OSStatus could be cryptic to understand, Apple provides an additional API called
SecCopyErrorMessageString(_:_:) to obtain a human-readable string corresponding to these status codes.
Now that you have a solid grasp of Keychain Services, in the next section you’ll learn how to remove the stubbed methods provided by your wrapper.