How to save Sensitive Data in Keychain in iOS using Swift

Last updated on: May 27, 2023

Keychain is an encrypted database. You can store user’s sensitive information, such as credit card numbers, authentication tokens, passwords, or even short notes, without the fear of someone else from outside will have access to them.

In iOS, there’s only one keychain (which includes your iCloud keychain). The keychain is automatically locked/unlocked when the user locks/unlocks their device.

To “talk” to the database (add, retrieve, update, delete), we are doing it through the Keychain Services API.

Keychain Services is a part of Apple’s Security Framework

Keychain Services contains 2 main parts:

How it works

When you store data in the database, you need to “say” what you are going to save.
For example, if we want to save the user’s password, we can “say” it using the item class kSecClassGenericPassword.

There are 5 types of item class:

If you click one of the links above, you’ll see a page like the following:

kSecClassGenericPassword is an item class that you need to set when you add items in the keychain

Under the Discussion section, this list is the list of attributes that this class supports.
Each item class has its own attributes.

We use these attributes as an identifiers or metadata for the data we’re going to save.

In our example (password), we’re going to use the kSecAttrAccount attribute and use the user’s username as an identifier.

And at the end, you’re “saying” the value that you want to store (e.g., password) using the kSecValueData

Saving Item

When you add an item in the database, keychain services encrypt the data first and then wraps it together with the attributes.

To save an item, we use the function: func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus

import Security

// Set username and password
let username = "john"
let password = "1234".data(using: .utf8)!

// Set attributes
let attributes: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: username,
    kSecValueData as String: password,
]

// Add user
if SecItemAdd(attributes as CFDictionary, nil) == noErr {
    print("User saved successfully in the keychain")
} else {
    print("Something went wrong trying to save the user in the keychain")
}Code language: Swift (swift)

If you want, you can tag your item using the kSecAttrLabel attribute to find it easier later on.

Retrieving Item

We can find an item by searching the attributes we added with the data when we stored it in the database.

In this example, we are retrieving the item by checking if the user with the username “john” exists in the keychain. If it exists, we decrypt the results and print them.

Don’t forget that you need to set the class of the item you’re retrieving the same as the item class you used when you saved it (e.g., kSecGenericPassword)

import Security

// Set username of the user you want to find
let username = "john"

// Set query
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: username,
    kSecMatchLimit as String: kSecMatchLimitOne,
    kSecReturnAttributes as String: true,
    kSecReturnData as String: true,
]
var item: CFTypeRef?

// Check if user exists in the keychain
if SecItemCopyMatching(query as CFDictionary, &item) == noErr {
    // Extract result
    if let existingItem = item as? [String: Any],
       let username = existingItem[kSecAttrAccount as String] as? String,
       let passwordData = existingItem[kSecValueData as String] as? Data,
       let password = String(data: passwordData, encoding: .utf8)
    {
        print(username)
        print(password)
    }
} else {
    print("Something went wrong trying to find the user in the keychain")
}Code language: Swift (swift)

Updating Item

To update an item, you query the items, and if you find it, you can change the data using the function: func SecItemUpdate(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus

import Security

// Set username and new password
let username = "john"
let newPassword = "5678".data(using: .utf8)!

// Set query
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: username,
]

// Set attributes for the new password
let attributes: [String: Any] = [kSecValueData as String: newPassword]

// Find user and update
if SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == noErr {
    print("Password has changed")
} else {
    print("Something went wrong trying to update the password")
}Code language: Swift (swift)

Deleting Item

Deleting an item works the same as updating it but without setting the attributes and with the use of the function: func SecItemDelete(_ query: CFDictionary) -> OSStatus

import Security

// Set username
let username = "john"

// Set query
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: username,
]

// Find user and delete
if SecItemDelete(query as CFDictionary) == noErr {
    print("User removed successfully from the keychain")
} else {
    print("Something went wrong trying to remove the user from the keychain")
}Code language: Swift (swift)
You can find the final project here

If you have any questionsplease feel free to leave a comment below

Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments