Mac O’Clock

The best stories for Apple owners and enthusiasts

Follow publication

Retrieve multiple values from Keychain

Hi everybody, I’m Riccardo. Senior iOS Engineer at Bending Spoons, I breathe iOS development, both apps and tools and I love to share my knowledge with others.

The keychain is a part of the disk that can be used to store application secrets: what is saved there is encrypted by the OS and cannot be accessed from outside the application. It is really useful to store, for example, the password used by the user to log in into some services or the access token returned by an authentication service.

However, the keychain has a set of API that is really ugly: you can think about it as a database for the secrets. How do we access a database? Yes, with queries! Let’s take a look at the basic API to insert, update, delete, and retrieve a value from the keychain.

Basic API

Insert a value

To Insert a value in the keychain, we have to create a query that tells the Secure API what kind of Secret we want to save, what are its key and its value. This is done by creating a dictionary [String: Any where all this info can be specified. An example of how to store a generic password could be:

Let’s analyze the code above:

In the first step, we are going to create a query dictionary. The shape of the dictionary changes based on what you want to store that is identified by the kSecClass attribute. The APIs let you store different things:

  • Passwords: these are identified by the kSecClassGenericPassword or the kSecClassInternetPassword values. They require the presence of a kSecAttrService for the services where the passwords are used, and a kSecAttrAccount for the accounts where they are used.
  • Identities: these are identifier by thekSecClassIdentity value and can be used to save the identities of some certificates.
  • Certificates: these are identified by the kSecClassCertificate value.
  • Keys: these are public, private, or symmetric keys and are identified by the kSecClassKey value.

Notice that every one of these classes can have some optional values that can be added to provide more information about them, like thekSecAttrService and in the kSecAttrAccount keys we are adding to store our passwords. Finally, we can store the value of the password in the kSecValueData key.

The second step is to actually store the password. This is done by using the SecItemAdd(_:,_:) function. This function takes two parameters: the query dictionary and a pointer. By specifying some additional parameters is possible to ask the function to return some information about the stored password that is used to valorize the second parameter. However, this is usually ignored.

The SecItemAdd function returns an OSStatus value with the outcome of the operation carried on. We can typically interpret those results with the errSecXXX values that are static variables for these statuses. However, sometimes the function fails with some hard-to-interpret values: in those cases, we can use the helpful osstatus.com website that performs a search on those statuses, telling us what’s happening.

In the example, we make sure that the status should be a duplicate item, in case the value is already there, or a success. In all the other cases we raise a fatal error. Remember: this is just an example! In a real app, we can decide to raise an error to handle it.

Delete a password

The snippet of code to delete a password is quite simple:

Again, we have to first create a query dictionary to delete the password for a specific service, associated with an account. Therefore, we specify the kSecClassGenericPassword and we pass the service and account values of the account we want to delete.

In this case, the primitive to delete the value is SecItemDelete(_:) that only takes the query dictionary as a parameter.

Update a password

I think you got how this kind of queries work now, am I right? To update a password, we can use this snippet of code:

This looks exactly like the store, but the primitive function to update the password is SecItemUpdate(_:, _:) instead of SecItemAdd(_:, _:). What’s the difference between the two? The update fails if it can’t find a value in the keychain that can be updated; the add fails if there is already an item in the keychain that satisfies the query in the dictionary.

Retrieve a Password

Finally, let’s see how can we retrieve a single password that we have stored.

The query dictionary is slightly different in this case: we have to add the kSecReturnData key with a value of true. This will instruct the primitive that it has to return a value if the query is successful.

The way in which we obtain the associated data is through an inout parameter. That’s why step 2 creates an AnyObject variable that is used to store the result of the query.

In step 3 we invoke the primitive to retrieve the data. This primitive is named SecItemCopyMatching(_:, _:) and it takes the query dictionary and the address of the inout variable as parameter. It copies the item that matches the query criteria into the second parameter of the function.

Finally, we handle the result of the query: by raising a fatal error if something went wrong, by returning nil if there is no entry associated with the query or by returning the actual data associated with the query if anything has been found.

Work with multiple values

Until now, we described the basic mechanisms that we can use to store and retrieve single secrets in the keychain. However, there could be some cases where we would like to retrieve similar secrets that we stored in the keychain.

I have found two attributes that we can use to do so:

  1. kSecAttrType: this attribute lets the user define the type of secret associated with the query. It’s an integer and we can use this key to retrieve all the secrets with the same type.
  2. kSecAttrLabel: this is a string that we can associate with the secrets. In this case, we can attach a label to all the secrets of a specific category and retrieve all these secrets with a single query.

After choosing the strategy we prefer, type versus label, we have to update our code to support them so that we store this additional info when we save our secrets.

Type: Add and Update

If we choose to go with the type approach, we just have to update the functions of the previous snippets, passing this new parameter to the functions:

Label: Add and Update

The same thing happens, if we want to go with the label approach:

Reading multiple values

At this point, we need to read all the values with a given type or label. In the read query we have to specify:

  1. the strategy adopted, whether it is the Type or the Label one.
  2. the number of results we want to obtain. This is specified by the kSecMatchLimit key, whose default value is 1. The accepted values for this key are numbers or the constant kSecMatchLimitAll. If the latter is specified, the Keychain will try to retrieve all the results that match with the specified query.

The code will change to the following:

As you can see, the returned type is a CFArray. This is an immutable array from the Objective-C runtime. To work with it, we first have to convert it to a standard Swift array. We can do this with the following snippet:

At this point, the careful reader would question: ok, with this approach I can retrieve all the values of the secrets that are grouped by the same label/type. But how can I retrieve the keys associated with each value?

The answer is pretty easy: we just have to requests all the attributes (using the kSecReturnAttributes key) and the data (specified with the kSecReturnData key) altogether.

At this point, the result of the SecCopyItemMatching(_: _:) is a CFArray that contains CFDictionaries and not just Data. The dictionary can be explored by using proper keys, whose constant values are the same used for the query. So, the code can be updated as follow:

And that’s it! We now have all the ingredients to store different secrets that share a label or a type in the keychain, and we have a way to retrieve them all in a single query!

I hope someone will find this article useful to better understand how the keychain work and how to perform some advanced operation with it.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Mac O’Clock
Mac O’Clock

Published in Mac O’Clock

The best stories for Apple owners and enthusiasts

Riccardo Cipolleschi
Riccardo Cipolleschi

Written by Riccardo Cipolleschi

Hey there, I’m Riccardo. Software engineer at Meta. I have a passion for iOS and I love to share my knowledge with others.

Responses (2)

Write a response