As we established in our prior blog post, the Keychain Services framework is like a trusted vault, where you can securely store sensitive information such as passwords, tokens, and credit card details. To interact with this vault, we needed to understand its CRUD methods and now in this post we will extensively unpack it’s language – the Keychain
query attributes.
Keychain’s Unified Querying Approach
One of the distinctive features of the Keychain framework is its simplicity in handling CRUD operations through queries that are represented as dictionaries of key-value pairs. By attaching the necessary information in these queries, we can perform various Keychain tasks without the need for complex interfaces or extensive code. The SecItemAdd
, SecItemUpdate
, SecItemCopyMatching
, and SecItemDelete
, all rely on query dictionaries to do their jobs.
An example of a Keychain query:
1
2
3
4
5
6
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "[email protected]",
kSecValueData as String: passwordData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
Components of a Keychain Query
When storing data in Keychain we usually need to ask three questions:
- What kind of data do we want to store?
- What unique ID do we want to store it under?
- And what other security / access control measures do we want to put in place to secure the data?
Now this is where the Keychain query attributes typically comes in, it allows us to give the Keychain store the answer to those questions.
kSecClass: What kind of data do you want to store?
Here, Keychain is referring to the type of data we wish to keep safe. It could be a password, an internet password (for websites or APIs), a certificate, a cryptographic key, or even an identity consisting of a private key and a certificate.
To answer this question, we use the "kSecClass"
key, which acts like a code word for the data type. We tell Keychain what we want to store by setting this key to one of the predefined values like kSecClassGenericPassword
for passwords or kSecClassInternetPassword
for internet credentials. Check out apple doc for an extensive list of values we can select for the kSecClass key.
1
2
3
4
5
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword, // For passwords
// Other attributes...
]
kSecAttrAccount: What ID do you want to store it under?
Now, Keychain wants to know the unique identifier for the data we’re putting into its vault. This ID helps Keychain keep everything organized, ensuring that our data can be easily retrieved later.
To answer this question, we use the kSecAttrAccount
key. This key serves as a label, and we tell Keychain the specific ID we want to associate with our data. For example, if we’re storing a password for a user account, the kSecAttrAccount
could be the user’s email address or username.
By responding to these two questions, we can communicate our intentions to Keychain, and it will securely store our sensitive data in its vault.
1
2
3
4
5
6
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "[email protected]", // Unique ID for the data
// Other attributes...
]
kSecReturnData: Do you want to fetch the stored data?
This attribute determines whether the query should return the retrieved data upon completion. We need to set this to true if we want to retrieve the stored data along with other attributes. We usually use this when querying with the SecItemCopyMatching
method which is responsible for retrieving data from keychain.
1
2
3
4
5
6
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "creditCard123",
kSecReturnData as String: true, // telling keychain to return the stored data when this query is applied
// Other attributes...
]
kSecAttrAccessible: When can the data be accessed?
Keychain wants to know if there are any restrictions on when our data can be accessed. For instance, we might want the data accessible only when the device is unlocked or even after the first unlock.
To address this, we use the kSecAttrAccessible
key. We can set it to different values like kSecAttrAccessibleWhenUnlocked
or kSecAttrAccessibleAfterFirstUnlock
to define the accessibility level.
1
2
3
4
5
6
7
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "[email protected]",
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked, // Accessible only when the device is unlocked
// Other attributes...
]
kSecAttrAccessControl: Do you want to set any special access controls?
Sometimes, we may need to impose extra security measures on our data, such as requiring biometric authentication or user presence.
To handle this, Keychain offers the kSecAttrAccessControl
key. We can use it to specify access control options for the data item, ensuring our data stays protected under specific conditions.
1
2
3
4
5
6
7
8
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "[email protected]",
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrAccessControl as String: myAccessControl, // Custom access control settings
// Other attributes...
]
As you can see from these code examples, by attaching the correct query attribute, we can easily interact with the Keychain framework to securely store and get sensitive data as needed.
Common Query Attribute Keys
Here’s a list of the common dictionary keys we can attach to a Keychain query to convey our intentions to the Keychain Service framework:
kSecClass
: Specifies the class of the keychain item and determines the type of data we want to store. The available options are:kSecClassGenericPassword
: Used for storing generic passwords or sensitive data.kSecClassInternetPassword
: Used for storing internet passwords, such as credentials for websites or APIs.kSecClassCertificate
: Used for storing X.509 certificates.kSecClassKey
: Used for storing cryptographic keys.kSecClassIdentity
: Used to store an identity, which includes a private key and a certificate.
-
kSecReturnData
: Determines whether the query should return the data alongside the item attributes. Set this totrue
if you want to retrieve the stored data along with other attributes. -
kSecAttrAccount
: Represents the user-defined account name associated with the data item. This attribute is used to uniquely identify the keychain item. -
kSecValueData
: Contains the sensitive data we want to store in the keychain. It is usually provided as a Data object in Swift. kSecAttrAccessible
: Defines the accessibility of the keychain item, determining when the data can be accessed. Available options include:
kSecAttrAccessibleWhenUnlocked
: The item can be accessed only when the device is unlocked. This is the default option.kSecAttrAccessibleAfterFirstUnlock
: The item is accessible once the device has been unlocked for the first time after a restart.kSecAttrAccessibleAlways
: The item is always accessible, even when the device is locked.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
: The item is accessible only when a passcode is set on the device, and it’s unique to this device only.
Note: The actual list of available options might vary depending on the device and iOS version please check apple doc for more info.
-
kSecAttrAccessControl
: Provides access control options for the keychain item, allowing fine-grained security measures. It is an optional attribute and is used when you want to specify additional security constraints, like biometric authentication. Access control options are defined using the Security framework’sSecAccessControlCreateWithFlags
function. -
kSecMatchLimit
: Specifies the match limit when using SecItemCopyMatching. It determines how many matching items should be returned. Available options include:
kSecMatchLimitOne
: Returns the first item that matches the keychain query.kSecMatchLimitAll
: Returns all matching items.
Closing Thoughts
Hopefully this blog post has helped you demystify how to work with keychain services, check out apples doc for my info on the topic. Thanks for reading!