Home Articles

Master iOS Keychain: The Ultimate Guide to Secure User Data Storage

Securing User Data: A Practical Guide to Keychain Implementation for Safeguarding User Data

Demo of Keychain Services iOS

Demo of Keychain Services iOS

Keychain Services

Securely store small chunks of data on behalf of the user.

Generally, we used to remember or memorize certain passwords for our Internet banking or user accounts. Sometimes, we overuse the same credentials for multiple login accounts. This is considered an insecure practice.

Keychain services API helps to solve our problem. A mechanism to store small bits of user data in an encrypted database called a keychain.

Keychain operations are Thread safe and that is guaranteed by Apple.

There is an API component we should be clear with.

SecKeychainItem

An opaque type that represents a keychain item.

Keychain items — Embed confidential information in items that you store in a keychain. In simpler words, items — insert/save to the database.

                                                            Apple Developer Documentation

Apple Developer Documentation

We will be designing a sign-in feature with a username and password.

For Username:

kSecAttrAccount — A key whose value is a string indicating the item’s account name.

let kSecAttrAccount: CFString

Global Variable - item’s account name

For Password:

kSecValueData — A key whose value is the item’s data.

let kSecValueData: CFString

Global Variable - item’s data

You must be wondering what is CFString !!!

CFString(Core Foundation String): is part of Apple’s Core Foundation framework, a C-based framework. It follows a reference-counted memory management model.

Saving a Keychain Item

To save an item, we can use the function.

This method will then return an OSStatus that indicates the status of the save operation.

If we get the errSecSuccess status, it means that the data has been successfully saved to the keychain.

SecItemAdd(::)

func SecItemAdd(
    _ attributes: CFDictionary,
    _ result: UnsafeMutablePointer<CFTypeRef?>?
) -> OSStatus

Function - SecItemAdd

attributes

A dictionary that describes the item to add.

The item’s class — You use the kSecClass key to tell the Keychain services to store such as passwords, cryptographic keys and so on. Refer documentation.

kSecAttrAccount — A key whose value is a string indicating the item’s account name. In our example, we use the username.

Use the kSecValueData key to indicate the data you want to store. Keychain services will take care of the encryption of the data and store it accordingly.

func addKeychainItem(attributes attrs: CFDictionary,
                     _ completion: @escaping (OSStatus, CFTypeRef?) -> Void) {
    queue.async {
        var item: CFTypeRef?
        let result = SecItemAdd(attrs, &item)
        completion(result, item)
    }
}

SecItemAdd - Add Keychain Item Example

Searching for Keychain Item

To search for the keychain item, we use the following function.

Returns one or more keychain items that match a search query, or copies attributes of specific keychain items.

SecItemCopyMatching(::)

func SecItemCopyMatching(
    _ query: CFDictionary,
    _ result: UnsafeMutablePointer<CFTypeRef?>?
) -> OSStatus

Function - SecItemCopyMatching

query

The item’s class — use the kSecClass

attributeskSecAttrService

Search parameters — We can limit the search results to a specific number of items. Refer documentation. In this example, I will be using the kSecMatchLimitOne

Return types — to return all data and attributes for the found value.

func findKeychainItem(attributes attrs: CFDictionaryRef,
                      _ completion: @escaping (OSStatus, CFTypeRef?) -> Void) {
    backgroundQueue.async {
        var item: CFTypeRef?
        let result = SecItemCopyMatching(attrs, &item)
        completion(result, item)
    }
}

Finding Keychain Item using SecItemCopyMatching

Updating the existing Keychain Item

To search and update the keychain item, we use the following function. To update the keychain item, we must identify the right item. An update begins with an implicit search just like the one explicitly performed by the SecItemCopyMatching

SecItemUpdate(::)

func SecItemUpdate(
    _ query: CFDictionary,
    _ attributesToUpdate: CFDictionary
) -> OSStatus

Function - SecItemUpdate

query

The item’s class — You use the kSecClass

kSecAttrAccount — A key whose value is a string indicating the item’s account name. In our example, we use the username.

Search parameters to search the keychain item to update.

attributesToUpdate

Use the kSecValueData key to indicate the data you want to store. Keychain services will take care of the encryption of the data and store it accordingly.

let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
guard status != errSecItemNotFound else {
    throw KeychainError.noPassword
}
guard status == errSecSuccess else {
    throw KeychainError.unhandledError(status: status)
}

Updating existing item using SecItemUpdate

Deleting the existing Keychain item

To delete the keychain item, we use the following function. This is a straightforward solution. Deleting an item from the keychain will be similar to updating the keychain item except that it only requires the query Dictionary.

SecItemDelete(_:)

func SecItemDelete(_ query: CFDictionary) -> OSStatus

Function using SecItemDelete

query

The item’s class — use the kSecClass

Search parameters to search the keychain item to delete.

let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
    throw KeychainError.unhandledError(status: status)
}

Deleting item using SecItemDelete

Note

Please find the GitHub Repo

This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.