Password-protected entries in iOS keychain

Alexei Gridnev
5 min readApr 3, 2019

Quite often an iOS app needs to store some sensitive data like an API access token or a local DB encryption key. Storing this kind of data on filesystem or in UserDefaults definitely isn’t a safest decision. The best option is to use iOS keychain — a secure storage system specifically designed for such cases.

iOS keychain allows each application to store small bits of its private data as a set of entries, each entry having it own name and access rules.

In this article we’re going to talk about creating and later accessing keychain entries protected by a password.

This feature was introduces in iOS 9. You can find out more about new keychain API capabilities introduced in iOS 9 here:

Password-protected entries may be useful when you want user to enter a password to access some local data. For example you can encrypt your database and store the encryption key in a keychain entry (if for some reason you don’t want to directly derive the encryption key from the password).

Also it can be useful as a fallback option for the case when biometry (Touch ID or Face ID) is unavailable (disabled by user) but you still want more security, than provided by a normal keychain entry, whose contents are available to the app always or all the time the app is unlocked.

What do we need to create a password-protected keychain entry? Here are the steps:

  • Create an LAContext instance and call setCredential on it with your password as an argument;
  • Create a SecAccessControl instance for our keychain entry indicating that we want it to be protected by a password;
  • Put the LAContext and SecAccessControl instances into a dictionary, add some other required values and call SecItemAdd with that dictionary as the first argument.

Here’s how it looks:

Here our SecAccessControl instance defines the conditions (user-provided password requirement) under which the app can access the keychain entry.

In this Apple doc you can find more info about available ways of protecting keychain items:

Now, how can we read that entry? There are several slightly different ways to do it. They all require a SecItemCopyMatching function call, the difference is only in parameters we supply to that function. To demonstrate all these approaches we will be using the following method:

It prepares all the required arguments for SecItemCopyMatching and calls the function to read the keychain entry data.

Simple SecItemCopyMatching call

Let’s look at the first way how we can use this method. Here we won’t be creating and supplying our own LAContext instance, we will use nil instead. So in our query dictionary kSecUseAuthenticationContext and kSecUseAuthenticationUI keys will be missing:

When SecItemCopyMatching is called, iOS displays a special UI prompt asking for the password. It looks like this:

Instead of “keychain-sample” you’ll have your app’s name.

Note that in this case the SecItemCopyMatching call is synchronous and it has to be done on a background thread to prevent the main thread from blocking.

If user enters a wrong password then iOS automatically re-displays the prompt. SecItemCopyMatching call won’t return until user enters the correct password or taps “Cancel” or exceeds the maximum allowed number of attempts (defined by the system, in my case it was 5 if I’m not mistaken).

Using LAContext.evaluateAccessControl

If we want to control the number of attempts manually and also avoid the long-running blocking call SecItemCopyMatching we can use a slightly different approach:

Here we create an LAContext instance and call evaluateAccessControl with operation: .useItem for it. This evaluateAccessControl call will trigger the same UI prompt as we had previously. Subsequent SecItemCopyMatching call won’t be blocking however.

Also it is worth noting that the localizedReason parameter for some reason is ignored in this evaluateAccessControl call and the UI prompt looks exactly the same as in the previous case.

The key point in preparing the data for the SecItemCopyMatching is to set our LAContext instance as a value for the kSecUseAuthenticationContext key and also set kSecUseAuthenticationUIFail value for kSecUseAuthenticationUI.

In this case our loadPassProtected call will fail after the first wrong password attempt and we’ll have to repeat the procedure manually if we want to allow the second attempt. Thus we can control the number of attempts.

By the way, if we don’t use kSecUseAuthenticationUIFail in the SecItemCopyMatching call, the system will automatically handle wrong passwords the same way as we had it before. And SecItemCopyMatching will become blocking.

Custom UI instead of system prompt

Now what if the system password prompt doesn’t play well with our app design? It appears that we can implement our own UI for password input and just supply the password value to the keychain API.

To do this we have to create an LAContext instance and set the password obtained from user to it. Just the same way as we were doing it when we were saving the entry in keychain:

Here we also use a non-blocking SecItemCopyMatching call.

It also should be noted that you need a real device to test this code. It doesn’t work in simulator. I’ve seen mentions that it won’t work on a real device with passcode turned off, here for example:

But my tests show that at least in iOS 12 it works fine when I turn off the passcode on my device.

Sample code for creating and reading password-protected entries as described in this post can be found in this project on github:

I hope this information can be useful to make your iOS app more secure. Please let me know if you find an issue or have any suggestions.

--

--