Saving Passwords in the Keychain in Swift

The Keychain is the place to store small amounts of data securely, such as passwords and API tokens. In this article you’ll learn how to work with the Keychain in iOS and Mac apps using the Keychain Services framework.

Parts of a Keychain Action

There are four common actions to perform on keychain items: add an item, update an item, read an item, and delete an item. To perform a keychain action, create a query and run a Keychain Services function.

Query

A query tells Keychain Services what you want to do. A query is a Swift dictionary that you must cast to CFDictionary.

Every query must include an entry with the key kSecClass, which specifies the kind of item to add, update, read, or delete. There are the following Keychain item types:

  • Generic password, kSecClassGenericPassword
  • Internet password, kSecClassInternetPassword
  • Certificate, kSecClassCertificate
  • Cryptographic key item, kSecClassKey
  • Identity item, kSecClassIdentity

I’m going to focus on generic passwords in this article, as that’s what most apps use. You’re not limited to passwords when using generic passwords. I was able to use generic passwords to store OAuth tokens in the Keychain.

The query keys you can use depend on the class. You can find a list of possible keys in the following section of Apple’s documentation:

Item Class Keys and Values

Click the link for a class to see the available items. You can also read the documentation in Xcode by choosing Help > Developer Documentation.

Two common item attributes for generic passwords are services, kSecAttrService, and accounts, kSecAttrAccount.

The value you supply for the service is the text that appears for the item in the Keychain Access app on Mac. Make sure the text clearly shows the keychain item is part of your app. A generic value like password will be hard to find in the Keychain Access app.

The value for the account is the name of the password’s account. If you’re writing a Mastodon client, Mastodon would be a good value for the account.

Keychain Services Functions

Call the following functions to work with the Keychain for passwords:

  • SecItemAdd to add an item to the Keychain
  • SecItemUpdate to update an existing Keychain item
  • SecItemCopyMatching to read an item from the Keychain
  • SecItemDelete to delete an item from the Keychain

Adding a Keychain Item

Start by importing the Authentication Services framework. The Keychain Services API is in the Authentication Services framework.

import AuthenticationServices

You should also create a class for the Keychain functions.

Let’s start by writing a function to add an item to the Keychain.

func save(_ data: Data, service: String, account: String) {
  let query = [
    kSecValueData: data,
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecAttrAccount: account
  ] as CFDictionary
            
  let saveStatus = SecItemAdd(query, nil)
    
  if saveStatus != errSecSuccess {
    print("Error: \(saveStatus)")
  }
        
  if saveStatus == errSecDuplicateItem {
    update(data, service: service, account: account)
  }
}

The function starts by building a query. Tell Keychain Services that you’re adding a generic password. Supply the data, service, and account. The data is what you want to save in the Keychain.

After building the query, call the function SecAddItem to add the item to the Keychain. If the item is added to the Keychain successfully, SecAddItem returns the value errSecSuccess.

If the item already exists in the Keychain, SecAddItem returns the value errSecDuplicateItem. In this case update the existing item, which I cover next.

Updating an Existing Keychain Item

Let’s look at the code to update a keychain item.

func update(_ data: Data, service: String, account: String) {
  let query = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecAttrAccount: account
  ] as CFDictionary
            
  let updatedData = [kSecValueData: data] as CFDictionary
  SecItemUpdate(query, updatedData)
}

Notice that the update function doesn’t include the data in the query. The code creates another dictionary for the data and passes it as an argument to the function SecItemUpdate.

Reading an Item from the Keychain

Saving items to the Keychain isn’t going to help unless your app can read the keychain items.

func read(service: String, account: String) -> Data? {
  let query = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecAttrAccount: account,
    kSecReturnData: true
  ] as CFDictionary
            
  var result: AnyObject?
  SecItemCopyMatching(query, &result)
  return result as? Data
}

The big difference in the query is the kSecReturnData key, which tells Keychain Services you want to return data from the function call. The function SecItemCopyMatching returns its results as type AnyObject?. You saved the item as Data so you should return a Data object. If there is no matching item in the Keychain, SecItemCopyMatching returns nil.

Deleting an Item from the Keychain

The last major task to perform on Keychain items is to delete items from the Keychain.

func delete(service: String, account: String) {
  let query = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecAttrAccount: account
  ] as CFDictionary
            
  SecItemDelete(query)
}

The query for deleting an item is the simplest one. Supply the class, service, and account. Call SecItemDelete to delete the item from the Keychain.

Get the Swift Dev Journal Newsletter

Subscribe and get exclusive articles, a free guide on moving from tutorials to making your first app, notices of sales on books, and anything I decide to add in the future.

    We won't send you spam. Unsubscribe at any time.