How to save a Realm Encryption Key to Keychain in Swift

Joshua Hart
9 min readFeb 15, 2023

In software development, data security is of utmost importance. To keep user data secure, developers rely on encryption techniques to secure user data. In the case of iOS development, the Keychain is a secure storage system for sensitive data such as passwords, tokens, and encryption keys.

An encryption key is a secret code used to encrypt and decrypt data. It’s essential to keep the encryption key secure and not accessible to unauthorized users. In iOS, the Keychain provides an excellent way to store encryption keys securely. In this article, we’ll explore how to save an encryption key to Keychain in Swift.

Step 1: Create a Keychain helper class

Everything is always easier when we know exactly where our code is located in XCode and what its intended use is for.
In this use-case we can create a struct called ‘Keyring’. I like this naming convention because if you imagine a keychain in real life:

You → Keyring → Keychain → Your Car Keys

In XCode it will be similiar:

Your App → Keyring → Keychain → App Sensitive Data

The first step is to create the Keyring struct. To do this, add the following code to your project:

import Foundation

/// Keyring manages our app's access to the Keychain.
struct Keyring {


}

Step 2: Generate an Encryption Key

The second step is to generate an encryption key for Realm that you want to save in the Apple Keychain. Realm suggests to always create a random encryption key and for it to be 512-bits of data. Add the following code to our Keyring.swift file:

/// Generates a new encryption key for realm.
/// Using the Apple's Security framework this function will generate data that consists of
/// 64 sections of randomized 8-bit data.
/// - Returns: 512 bits of randomized data.
private static func generateRealmEncryptionKey() -> Data? {
var bytes = [UInt8](repeating: 0, count: 64)
let result = SecRandomCopyBytes(kSecRandomDefault, 64, &bytes)

guard result == errSecSuccess else {
return nil
}

return Data(bytes)
}

In this code, we’re generating a random key of consisting of 64 sections of 8-bit data. Resulting in a 512 byte size data object. The encryption key returns an optional data value.

Step 3: Create the Keychain Query CFDictionary

The third step is to create our Core Foundation Dictionary that consists of the values that we want to save to the Keychain. Go ahead and add this to our Keyring.swift file.

// MARK: PRIVATE - WRITE - ENCRYPTION KEY FROM KEYCHAIN:

/// The query is a dictionary containing search criteria for the encryption key within the Keychain.
/// - kSecClass: Specifying that we want to store a private or public key in the Keychain.
/// - kSecAttrApplicationTag: Used for searching and filtering. We pass the 'realmEncryptionTag' to tell Keychain we're looking for the encryption key.
/// - kSecAttrKeySizeInBits: Used to tell Keychain that it should expect our key to be between 0 and 512-bits.
/// - kSecValueData: The encrypted key for Realm that we want to write to the Keychain.
/// - kSecAttrAccessible: When this data can be accessed. For our app we want the data to be available when in the background, thus we choose
/// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly so that the keychain will be available for background operations.
/// - kSecReturnData: specifies whether the data of the item being searched for should be returned.
/// - Returns: (CFDictionary) The core foundation dictionary required to save data to the Keychain.
private static func writeRealmCoreDictionary(_ encryptedKey: Data) -> CFDictionary {[
kSecClass: kSecClassKey,
kSecAttrApplicationTag: realmEncryptionTagData,
kSecAttrKeySizeInBits: bitsOfData,
kSecValueData: encryptedKey,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecReturnData: coreTrue] as CFDictionary
}

In this code, we’re creating the dictionary that will be saved to the Keychain, it consists of key/value pairs that help Keychain identify and manage our saved data.

Let’s take care of some of those errors that are popping up do to undefined values:

For the ‘realmEncryptionTagData’ property we will need to create a key that is specific to the data we want to store and fetch from the Keychain. Make sure to replace ‘yourappname’ within the tag to the name of your app:

/// This tag is specific to your app finding the encryption key in the Keychain.
/// - WARNING: Do not reuse this tag for other writes and fetches from the Keychain.
private static let realmEncryptionTag = "com.yourappname.realm.encryption.key"

/// The realm application tag as CFData.
/// Used to identify the Realm encryption key within the Keychain.
private static var realmEncryptionTagData: CFData {
realmEncryptionTag.data(using: .utf8)! as CFData
}

In the code above we are creating:
realmEncryptionTag: Our specific identifier for our encryption key as a String
realmEncryptionTagData:
Our realmEncryptionTag identifier for our encryption key converted to Data and then casted as CFData.

The next two computed properties are pretty straight forward:

/// How many bits of data our encryption key should be.
/// Since we want 64 sections of 8-bit data, we need 512.
/// - Returns: 512 as a CFNumber.
/// - Note: We determine this number in the 'generateRealmEncryptionKey' function.
private static var bitsOfData: CFNumber {
512 as CFNumber
}

/// True as a Core Foundation Boolean.
private static var coreTrue: CFBoolean {
true as CFBoolean
}

bitsOfData: The kSecAttrKeySizeInBits key expects a value of CFNumber, so we convert an integer of 512 and return it as a Core Foundation Number.

coreTrue: The kSecReturnData key expects a CFBoolean value. We want to pass true so that when we fetch our encryption key later, it will be returned as Data. We could have used the kCFBooleanTrue for the kSecReturnData value, but it being a force unwrapped value makes it a headache to work with.

What we should have so far:

import Foundation

/// Keyring manages our app's access to the Keychain.
struct Keyring {
/// This tag is specific to your app finding the encryption key in the Keychain.
/// - WARNING: Do not reuse this tag for other writes and fetches from the Keychain.
private static let realmEncryptionTag = "com.yourappname.realm.encryption.key"

/// The realm application tag as CFData.
/// Used to identify the Realm encryption key within the Keychain.
private static var realmEncryptionTagData: CFData {
realmEncryptionTag.data(using: .utf8)! as CFData
}

/// How many bits of data our encryption key should be.
/// Since we want 64 sections of 8-bit data, we need 512.
/// - Returns: 512 as a CFNumber.
/// - Note: We determine this number in the 'generateRealmEncryptionKey' function.
private static var bitsOfData: CFNumber {
512 as CFNumber
}

/// True as a Core Foundation Boolean.
private static var coreTrue: CFBoolean {
true as CFBoolean
}

// MARK: PRIVATE - WRITE - ENCRYPTION KEY FROM KEYCHAIN:

/// The query is a dictionary containing search criteria for the encryption key within the Keychain.
/// - kSecClass: Specifying that we want to store a private or public key in the Keychain.
/// - kSecAttrApplicationTag: Used for searching and filtering. We pass the 'realmEncryptionTag' to tell Keychain we're looking for the encryption key.
/// - kSecAttrKeySizeInBits: Used to tell Keychain that it should expect our key to be between 0 and 512-bits.
/// - kSecValueData: The encrypted key for Realm that we want to write to the Keychain.
/// - kSecAttrAccessible: When this data can be accessed. For our app we want the data to be available when in the background, thus we choose
/// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly so that the keychain will be available for background operations.
/// - kSecReturnData: specifies whether the data of the item being searched for should be returned.
/// - Returns: (CFDictionary) The core foundation dictionary required to save data to the Keychain.
private static func writeRealmCoreDictionary(_ encryptedKey: Data) -> CFDictionary {[
kSecClass: kSecClassKey,
kSecAttrApplicationTag: realmEncryptionTagData,
kSecAttrKeySizeInBits: bitsOfData,
kSecValueData: encryptedKey,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecReturnData: coreTrue] as CFDictionary
}

// - MARK: PRIVATE - GENERATING A NEW KEY:

/// Generates a new encryption key for realm.
/// Using the Apple's Security framework this function will generate data that consists of
/// 64 sections of randomized 8-bit data.
/// - Returns: 512 bits of randomized data.
private static func generateRealmEncryptionKey() -> Data? {
var bytes = [UInt8](repeating: 0, count: 64)
let result = SecRandomCopyBytes(kSecRandomDefault, 64, &bytes)
guard result == errSecSuccess else {
return nil
}

return Data(bytes)
}
}

Well done! We have all the components thus far for a write operation to the Keychain! Let’s put it together!

Step 4: Save the Encryption Key to Keychain

The final step is to save the encryption key to the Keychain. The following code will give us the ability to save the encryption key by utlizing the SecItemAdd:

/// A renaming of the Keyring return type.
typealias KeyringResult = (Result<Data, KeyringError>) -> Void

// - MARK: PRIVATE - GENERATING A NEW KEY:

/// Since we are generating a new key, we do not need to generate the key and then ask for it again from the Keychain.
/// Instead we generate the new key and return the key so that we can utilize it for Realm.
/// - Parameter result: (KeyringResult) If the encryption key was generated,
/// a new encryption key will be returned, else a KeyringError will be returned.
static func generateNewEncryptionKey(result: KeyringResult) {
guard let key = generateRealmEncryptionKey() else {
result(.failure(.failedToGenerateEncryptedKey))
return
}

let status = SecItemAdd(writeRealmCoreDictionary(key), nil)

guard status == errSecSuccess else {
if status == errSecDuplicateItem {
print("Keyring: The keychain already has a saved value with that identifier.")
}
result(.failure(.error(status: status)))
return
}

result(.success(key))
}

In this code, we’re using the SecItemAdd function to save the encryption key to the Keychain. The function returns errSecSuccess if the key is saved successfully. If there is an error, it returns an error code that you can use to debug the issue in the result. In order to utilize the error handler correctly, let’s create an enum above our Keyring struct:

enum KeyringError: Error {
case error(status: OSStatus)
case failedToGenerateEncryptedKey
}

Step 5: Final — Putting it altogether

We have all the ingredients now to:
• Generate a random 512-bit data encryption key
• Create a Core Foundation Dictionary for our encryption key.
• Save our encryption key to the Keychain with some error handling.

import Foundation

/// Error types for the Keyring implementation.
enum KeyringError: Error {
case error(status: OSStatus)
case failedToGenerateEncryptedKey
}

/// Keyring manages our app's access to the Keychain.
struct Keyring {
/// This tag is specific to your app finding the encryption key in the Keychain.
/// - WARNING: Do not reuse this tag for other writes and fetches from the Keychain.
private static let realmEncryptionTag = "com.yourappname.realm.encryption.key"

/// A renaming of the Keyring return type.
typealias KeyringResult = (Result<Data, KeyringError>) -> Void

/// The realm application tag as AnyObject.
/// Used to identify the Realm encryption key within the Keychain.
private static var realmEncryptionTagData: CFData {
realmEncryptionTag.data(using: .utf8)! as CFData
}

/// How many bits of data our encryption key should be.
/// Since we want 64 sections of 8-bit data, we need 512.
/// - Returns: 512 as a CFNumber.
/// - Note: We determine this number in the 'generateRealmEncryptionKey' function.
private static var bitsOfData: CFNumber {
512 as CFNumber
}

/// True as a Core Foundation Boolean.
private static var coreTrue: CFBoolean {
true as CFBoolean
}

// MARK: PRIVATE - WRITE - ENCRYPTION KEY FROM KEYCHAIN:

/// The query is a dictionary containing search criteria for the encryption key within the Keychain.
/// - kSecClass: Specifying that we want to store a private or public key in the Keychain.
/// - kSecAttrApplicationTag: Used for searching and filtering. We pass the 'realmEncryptionTag' to tell Keychain we're looking for the encryption key.
/// - kSecAttrKeySizeInBits: Used to tell Keychain that it should expect our key to be between 0 and 512-bits.
/// - kSecValueData: The encrypted key for Realm that we want to write to the Keychain.
/// - kSecAttrAccessible: When this data can be accessed. For our app we want the data to be available when in the background, thus we choose
/// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly so that the keychain will be available for background operations.
/// - kSecReturnData: Specifies whether the data of the item being searched for should be returned.
/// - Returns: (CFDictionary) The core foundation dictionary required to save data to the Keychain.
private static func writeRealmCoreDictionary(_ encryptedKey: Data) -> CFDictionary {[
kSecClass: kSecClassKey,
kSecAttrApplicationTag: realmEncryptionTagData,
kSecAttrKeySizeInBits: bitsOfData,
kSecValueData: encryptedKey,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecReturnData: coreTrue] as CFDictionary
}

// - MARK: INTERNAL - GENERATING A NEW KEY:

/// Since we are generating a new key, we do not need to generate the key and then ask for it again from the Keychain.
/// Instead we generate the new key and return the key so that we can utilize it for Realm.
/// - Parameter result: (KeyringResult) If the encryption key was generated,
/// a new encryption key will be returned, else a KeyringError will be returned.
static func generateNewEncryptionKey(result: KeyringResult) {
guard let key = generateRealmEncryptionKey() else {
result(.failure(.failedToGenerateEncryptedKey))
return
}

let status = SecItemAdd(writeRealmCoreDictionary(key), nil)

guard status == errSecSuccess else {
if status == errSecDuplicateItem {
print("Keyring: The keychain already has a saved value with that identifier.")
}
result(.failure(.error(status: status)))
return
}

result(.success(key))
}

// - MARK: PRIVATE - CREATING A NEW KEY:

/// Generates a new encryption key for realm.
/// Using the Apple's Security framework this function will generate data that consists of
/// 64 sections of randomized 8-bit data.
/// - Returns: 512 bits of randomized data.
private static func generateRealmEncryptionKey() -> Data? {
var bytes = [UInt8](repeating: 0, count: 64)
let result = SecRandomCopyBytes(kSecRandomDefault, 64, &bytes)

guard result == errSecSuccess else {
return nil
}

return Data(bytes)
}
}

Conclusion

In this article, we’ve learned how to save an encryption key to Keychain in Swift. The Keychain provides a secure way to store sensitive data. Especially when utilizing Realm.

Give back — Help this developer out!

Following me helps me reach my goal! Have any special requests? Want to know how to retrieve the encryption key from the Keychain?
Drop me a comment! Thanks!

--

--

Joshua Hart

I have been developing iOS mobile apps for over 10 years. I am a father of 5 and reside in Land O' Lakes, Florida.