Keychain Services provides secure storage of passwords, keys, certificates, and notes for one or more users. A user can unlock a keychain with a single password, and any Keychain Services–aware application can then use that keychain to store and retrieve passwords. ~ Apple Documentation
Keychain is a good place to put passwords, authentication keys or even whole models. But there is one thing that you need to be aware of. Keychain on iOS devices uses passcode to secure values stored inside it. On devices with no passcode it can be compromised with whole content, so it means that your passwords are compromised then to.
Apple provides a mechanism that allows you to avoid such situation. If you look into the documentation you will find an attribute called kSecAttrAccessible that decides if values that you want to put into the Keychain can be stored or read. It has four values:
When Passcode Set | When Unlocked | After First Unlock | Always
First one allows you to secure all your entries in Keychain. So when there is no passcode on device nothing can be stored inside of Keychain. Even when user decides to remove passcode it will automatically delete all previously stored values. Sounds good, isn’t it? But let’s assume that the device has no passcode and when you want to store user password in Keychain, you get an error, what then? It can break your flow or even whole your app.
There is a happy ending of this story.
I will show you how to combine Keychain storage with data encryption.
As a first step let’s create general keychain access protocol. Thanks to it we can use any library for keychain. Also this approach makes testing super easy.
Next step is to create cipher object whose main job is to encrypt and decrypt provided data using some key. In this case Cipher enum has two cases: root and generated. Root case is used to encrypt data using defined static 256-bit key. Generated case is used to encrypt data using some random key.
Below code is the heart of this example. This struct collects information about key name and also how to convert some generic type to/from Data.
In the extension you can find two methods:
set(keychain:value:) and get(keychain:).
Those methods are used to store and read values to/from keychain.
Now let’s define an example accessor, let say it’ll be email storage accessor. In encoding/decoding block I’m converting this from/to data.
If you look at this approach, it helps to ensure that a String only can be stored under email key. Additionally, you can define the “email” string key only in one place, moreover encoding/decoding blocks are easy to test.
Now let’s define a keychain for example purposes. In this case it’s a simple dictionary where key is String and value is Data. I’ve called it MockedKeychain because I use this approach in tests sutes.
And Finally! Accessing keychain value this way looks clean and safe!
This article was created in cooperation with Emil Wojtaszek, who is the author of the code example.
If you like my work hit ❤ button and let me know what you think in comments!