Radu Dan
A glimpse into the iOS Keychain

A glimpse into the iOS Keychain

Motivation

Ever wanted to store some information in a place where is secure? Maybe you heard that UserDefaults, CoreData are not that great in storing sensitive data, such as passwords, being more vulnerable to attackers.

What about a special place in your device called Keychain?! By special place we mean an encrypted database where you can put users data.

Each application on an iOS device have access to a single keychain. If you build an app you have access to your own items or those that are shared with a group which the app belongs to.

You are not limited storing just passwords. You can put in the keychain secrets, keys credit card information or notes.

In this article we will explore a part of the Keychain Services API and see how to store sensitive data like passwords or secrets.

If you want a simple wrapper to the Keychain, viewed as a key-value storage, I created a framework that you can use: github.com/radude89/KeychainStorage.
You can install it via CocoaPods, manually or through Carthage.

Security framework

To access the Keychain you will need to import the Security framework. It offers you capabilities of authorising and authenticating users, storing and retrieving secure data, code signing services and lower level cryptographic functions.

Overview Security Framework

Storing Data

Let's see an example:

        var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecValueData as String] = data
queryDictionary[kSecAttrAccount as String] = key

let status = SecItemAdd(queryDictionary as CFDictionary, nil)

The data you want to save will be packaged as a keychain item. It will have attached some attributes to control the accessibility and to make it retrievable.

Sotring Items

Let's see next what you will need to do to store a simple password in the keychain.

Transform the value to Data

        let password = passwordTextField.text!
let data = password.data(using: .utf8)!

Define the query attributes

A query attributes is a dictionary of [String: Any] key value pairs.

For our example, as a minimum way you can specify the service and the class:

  • kSecAttrService: used for generic passwords, represents the item's service, such as a label
  • kSecAttrAccount: attribute specific to Internet and generic passwords and defines the item's account name
  • kSecClass: defines the value type of the item you want to save.
    It can be:
    • generic password item (kSecClassGenericPassword)
    • Internet password item (kSecClassInternetPassword)
    • certificate item (kSecClassCertificate)
    • cryptographic key item (kSecClassKey)
    • identity item (kSecClassIdentity)

Add the value

You add the data object in the query dictionary, setting it to kSecValueData key. The item data object is cast from Swift.Data to a CFDataRef, a reference to an immutable CFData object.

        queryDictionary[kSecValueData as String] = data
let status = SecItemAdd(query as CFDictionary, nil)

You might spotted again CF in as CFDictionary.
CF stands for CoreFoundation and is part of a low-level framework bridged with Swift's Foundation framework. It is implemented in C language.

The item is added with the function SecItemAdd which returns an OSStatus. You usually verify the status if is of type success.

Retrieving data

Again, let's use an example to see how we retrieve our previously saved password:

          var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecAttrAccount as String] = key
queryDictionary[kSecMatchLimit as String] = kSecMatchLimitOne as String
queryDictionary[kSecReturnData as String] = true
queryDictionary[kSecReturnAttributes as String] = true

var result: AnyObject?
let status = SecItemCopyMatching(queryDictionary as CFDictionary, &result)

if status == errSecItemNotFound {
    return nil
}

guard status == noErr else {
    throw KeychainStorageError.unhandledError(status)
}

if let resultDictionary = result as? [String: Any],
    let data = resultDictionary[kSecValueData as String] as? Data {
    return data
}

Define the query attributes

In our example we used the following attributes:

  • kSecAttrService: same service as we used for storing the password
  • kSecClass: using generic password items
  • kSecAttrAccount: item's account name
  • kSecMatchLimit: it can be either kSecMatchLimitOne where a value will correspond to exactly one item or kSecMatchLimitAll where a value will correspond to an unlimited number of items
  • kSecReturnData: if set to yes, the item data will be returned
  • kSecReturnAttributes: if set to yes, it will return the item's attributes

Retrieve the value

For retrieving the value, we use the function SecItemCopyMatching.

The result object is an optional UnsafeMutablePointer<CFTypeRef?> which is cast to a Swift Dictionary. The actual data will be retrieved by getting the value found at kSecValueData key.

In Swift 5, UnsafeMutablePointer is a struct having a generic type Pointee. It is used to access data of Pointee type in memory (in our case CFTypeRef). We don't have an automated memory management, so we should be careful when using this kind of objects.

CFTypeRef gives a generic reference to any Core Foundation objects.

        typealias CFTypeRef = AnyObject
// or
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;

Deleting data

For deleting an item you can write something like:

        var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecAttrAccount as String] = key

let status = SecItemDelete(queryDictionary as CFDictionary)

if status != errSecItemNotFound && status != errSecSuccess {
    throw KeychainStorageError.unhandledError(status)
}

We use the SecItemDelete function that can delete one or more items if it matches the criteria.

Conclusion

We saw together some basic operations that you can do with Keychain Services API, such as store items, retrieve and delete passwords. You can check my open source project on GitHub: github.com/radude89/KeychainStorage, where you can seen an implementation of what we saw so far.

References