A glimpse into the iOS Keychain

Radu Dan
4 min readMay 29, 2019

--

Motivation

Need to store sensitive information securely? You might know that UserDefaults and CoreData aren't recommended for storing sensitive data like passwords due to their vulnerability to security threats.

Enter the Keychain: Apple’s encrypted database designed specifically for storing sensitive user data securely on your device.

On iOS, each app has its own isolated keychain space. When you develop an app, you can only access your app’s keychain items or those explicitly shared within an app group.

The Keychain isn’t just for passwords — you can store various types of sensitive data including secrets, encryption keys, credit card information, and secure notes.

In this article, we’ll explore the Keychain Services API and show you how to implement secure storage for sensitive data like passwords and secrets.

Looking for a simple solution? I’ve created a key-value wrapper framework for Keychain called KeychainStorage.
You can integrate it using Swift Package Manager or by manually adding the files to your project.

Security framework

To work with the Keychain, you’ll need to import the Security framework. This framework provides essential security features including user authentication, secure data storage, code signing services, and low-level cryptographic functions.

Using Security Framework to secure your sensitive data

Storing data

Here’s a basic example of storing data in the Keychain:

// Create a dictionary to store the keychain query parameters
var queryDictionary: [String: Any] = [:]

// Specify the service identifier for this keychain item
queryDictionary[kSecAttrService as String] = service

// Set the item class to generic password
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String

// Add the actual data to be stored
queryDictionary[kSecValueData as String] = data

// Set a unique identifier (key) for this keychain item
queryDictionary[kSecAttrAccount as String] = key

// Add the item to the keychain and get the status
// Status will be noErr (0) if successful
let status = SecItemAdd(queryDictionary as CFDictionary, nil)

When storing data in the Keychain, it’s packaged as a keychain item with specific attributes that control its accessibility and enable later retrieval.

Let’s break down the process of storing a password in the Keychain.

Transform the Value to Data

Before storing a value in the Keychain, we need to convert it to a Data object. Here's how we transform a password string:

// Get the password string from a text field
let password = passwordTextField.text!

// Convert the string to Data using UTF-8 encoding
let data = password.data(using: .utf8)!

Note: In production code, you should avoid force unwrapping (!) and instead handle these conversions safely:

guard let password = passwordTextField.text,
let data = password.data(using: .utf8) else {
// Handle the error case
return
}

The Keychain requires data to be stored as Data objects because it's a more secure way to handle sensitive information, and it allows for storing various types of data beyond just strings (like encryption keys or certificates).

Define the Query Attributes

In this example, the following attributes are used:

  • kSecAttrService: the same service used for storing the password
  • kSecClass: using generic password items
  • kSecAttrAccount: the account name associated with the item
  • kSecMatchLimit: set to either kSecMatchLimitOne (returns exactly one item) or kSecMatchLimitAll (returns an unlimited number of items)
  • kSecReturnData: if set to true, the item data will be returned
  • kSecReturnAttributes: if set to true, the item's attributes will be returned

Retrieve the Value

To retrieve the value, we use the SecItemCopyMatching function.

The result object is an optional UnsafeMutablePointer<CFTypeRef?> which is cast to a Swift dictionary. The actual data is retrieved from the value associated with the kSecValueData key.

UnsafeMutablePointer is a struct with a generic type Pointee. It accesses data of the Pointee type in memory (in this case, CFTypeRef). Since we don't have automatic memory management with this type, it's crucial to be cautious when using it.

CFTypeRef gives a generic reference to any Core Foundation object:

// Two equivalent definitions of CFTypeRef
typealias CFTypeRef = AnyObject
// or
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;

Deleting Data

To delete an item from the Keychain, use this code:

// Create a dictionary for the query parameters
var queryDictionary: [String: Any] = [:]

// Set the service identifier
queryDictionary[kSecAttrService as String] = service

// Specify we're looking for a generic password type
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String

// Set the key to find the specific item to delete
queryDictionary[kSecAttrAccount as String] = key

// Attempt to delete the item from the keychain
let status = SecItemDelete(queryDictionary as CFDictionary)

// Check for errors:
// - errSecItemNotFound: item doesn't exist (not an error in this case)
// - errSecSuccess: deletion successful
// - anything else: throw an error
if status != errSecItemNotFound && status != errSecSuccess {
throw KeychainStorageError.unhandledError(status)
}

The SecItemDelete function removes all items that match the specified query criteria from the Keychain.

Conclusions

We’ve explored the basic operations that can be performed with the Keychain Services API, including storing, retrieving, and deleting passwords. For a complete implementation of these concepts, check out my open-source project on GitHub.

--

--

Radu Dan
Radu Dan

Written by Radu Dan

iOS Developer / Doing magic things in Swift

No responses yet