A glimpse into the iOSÂ Keychain
Motivation
Ever wanted to store some information in a place where is secure? Maybe you heard that UserDefaults
, CoreData
are not that great in storing sensitive data, such as passwords, being more vulnerable to attackers.
What about a special place in your device called Keychain?! By special place we mean an encrypted database where you can put users data.
Each application on an iOS device have access to a single keychain. If you build an app you have access to your own items or those that are shared with a group which the app belongs to.
You are not limited storing just passwords. You can put in the keychain secrets, keys credit card information or notes.
In this article we will explore a part of the Keychain Services API and see how to store sensitive data like passwords or secrets.
If you want a simple wrapper to the Keychain, viewed as a key-value storage, I created a framework that you can use:
github.com/radude89/KeychainStorage.
You can install it via CocoaPods, manually or through Carthage.
Security framework
To access the Keychain you will need to import the Security framework. It offers you capabilities of authorising and authenticating users, storing and retrieving secure data, code signing services and lower level cryptographic functions.
Storing Data
Let's see an example:
var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecValueData as String] = data
queryDictionary[kSecAttrAccount as String] = key
let status = SecItemAdd(queryDictionary as CFDictionary, nil)
The data you want to save will be packaged as a keychain item. It will have attached some attributes to control the accessibility and to make it retrievable.
Let's see next what you will need to do to store a simple password in the keychain.
Transform the value to Data
let password = passwordTextField.text!
let data = password.data(using: .utf8)!
Define the query attributes
A query attributes is a dictionary of [String: Any]
key value pairs.
For our example, as a minimum way you can specify the service and the class:
kSecAttrService
: used for generic passwords, represents the item's service, such as a labelkSecAttrAccount
: attribute specific to Internet and generic passwords and defines the item's account namekSecClass
: defines the value type of the item you want to save.
It can be:- generic password item (
kSecClassGenericPassword
) - Internet password item (
kSecClassInternetPassword
) - certificate item (
kSecClassCertificate
) - cryptographic key item (
kSecClassKey
) - identity item (
kSecClassIdentity
)
Add the value
You add the data object in the query dictionary, setting it to kSecValueData
key. The item data object is cast from Swift.Data
to a CFDataRef
, a reference to an immutable CFData
object.
queryDictionary[kSecValueData as String] = data
let status = SecItemAdd(query as CFDictionary, nil)
You might spotted again CF in as CFDictionary
.
CF stands for CoreFoundation
and is part of a low-level framework bridged with Swift's Foundation framework. It is implemented in C language.
The item is added with the function SecItemAdd
which returns an OSStatus
. You usually verify the status if is of type success.
Retrieving data
Again, let's use an example to see how we retrieve our previously saved password:
var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecAttrAccount as String] = key
queryDictionary[kSecMatchLimit as String] = kSecMatchLimitOne as String
queryDictionary[kSecReturnData as String] = true
queryDictionary[kSecReturnAttributes as String] = true
var result: AnyObject?
let status = SecItemCopyMatching(queryDictionary as CFDictionary, &result)
if status == errSecItemNotFound {
return nil
}
guard status == noErr else {
throw KeychainStorageError.unhandledError(status)
}
if let resultDictionary = result as? [String: Any],
let data = resultDictionary[kSecValueData as String] as? Data {
return data
}
Define the query attributes
In our example we used the following attributes:
kSecAttrService
: same service as we used for storing the passwordkSecClass
: using generic password itemskSecAttrAccount
: item's account namekSecMatchLimit
: it can be eitherkSecMatchLimitOne
where a value will correspond to exactly one item orkSecMatchLimitAll
where a value will correspond to an unlimited number of itemskSecReturnData
: if set to yes, the item data will be returnedkSecReturnAttributes
: if set to yes, it will return the item's attributes
Retrieve the value
For retrieving the value, we use the function SecItemCopyMatching
.
The result object is an optional UnsafeMutablePointer<CFTypeRef?>
which is cast to a Swift Dictionary.
The actual data will be retrieved by getting the value found at kSecValueData
key.
In Swift 5, UnsafeMutablePointer
is a struct having a generic type Pointee
. It is used to access data of Pointee
type in memory (in our case CFTypeRef
). We don't have an automated memory management, so we should be careful when using this kind of objects.
CFTypeRef
gives a generic reference to any Core Foundation objects.
typealias CFTypeRef = AnyObject
// or
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;
Deleting data
For deleting an item you can write something like:
var queryDictionary: [String: Any] = [:]
queryDictionary[kSecAttrService as String] = service
queryDictionary[kSecClass as String] = kSecClassGenericPassword as String
queryDictionary[kSecAttrAccount as String] = key
let status = SecItemDelete(queryDictionary as CFDictionary)
if status != errSecItemNotFound && status != errSecSuccess {
throw KeychainStorageError.unhandledError(status)
}
We use the SecItemDelete
function that can delete one or more items if it matches the criteria.
Conclusion
We saw together some basic operations that you can do with Keychain Services API, such as store items, retrieve and delete passwords. You can check my open source project on GitHub: github.com/radude89/KeychainStorage, where you can seen an implementation of what we saw so far.
References
- Apple Documentation - Security Framework, Keychain Services
- Apple Documentation - Security Framework, Keychain Items
- Apple Documentation - Security Framework, Using the Keychain to Manage User Secrets
- Here you can find what a
OSStatus
number represents: (or check the internal documentation). - Question from StackOverflow, what makes a keychain item unique (in iOS)?