If you have ever developed an iOS application, chances are you have encountered a situation where you must securely store sensitive user information. In such cases, Apple's Keychain Services is an excellent tool as it provides robust security for safeguarding sensitive data. While UserDefaults suffice for managing preferences, it is crucial to avoid storing credentials within them. Keychain is not limited to passwords alone; it can securely store various personal data like credit card details or brief notes. Moreover, Keychain is capable of handling important items that the user may not be aware of, such as cryptographic keys and managed certificates. It's significant to note that Keychain is securely stored in the A7 chip or a newer version's secure enclave. While numerous wrappers are available to utilize Keychain functionality, I would like to demonstrate that it is also possible to implement it manually with relative ease.
Securing Keys with Keychain Storage
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:
- 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
- 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.
- Use the SecItemAdd(_:_:) function to actually store the item, if a problem occurs throw the error.
When you want to retrieve the key, create another query using the same application tag. To handle this let’s make another protocol:
Let’s take a look at the code:
- 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.
- 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
- Our key is returned as a Data object; let’s check it and convert it to String.
That’s it. Now let’s make a simple manager to create and retrieve keys for our purposes :)
Adding a password to Keychain
Keychain Services lets us enable simple, secure storage for user passwords. 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:
- 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, use a generic password instead.
- 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:
- 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
- After creating the query you call SecItemCopyMatching(_:_:) function to search your saved credentials.
- 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 shows how implementing your wrapper can be pretty easy and beneficial to users in 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 app development in upcoming blog posts.