First, it is better to specify the kSecAttrAccessible
key to indicates when the keychain item is accessible, like kSecAttrAccessibleWhenUnlocked
mentioned here.
And make sure the kSecAttrService
, kSecAttrAccount
, and kSecAttrSynchronizable
combined must be unique, as explained here.
That being said, the presence of kSecAttrSynchronizable
should not inherently cause a saving operation to fail. Typically, the best approach in these situations is to check the status returned by SecItemAdd
(or other Keychain API calls) to get detailed error information.
As noted in ahmed's answer, if you are saving a keychain item with specific attributes (like kSecAttrSynchronizable
set to kCFBooleanTrue
), then when you try to read (or query) that item, you must also specify the same attributes to successfully retrieve it. The attributes used in the save operation essentially set the "conditions" for that keychain item, and when you query for it, you need to match those conditions.
So, if you are saving a keychain item with kSecAttrSynchronizable
set to true
, your read/query function should also include kSecAttrSynchronizable
set to true
.
If your save function is:
let saveQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrSynchronizable as String: kCFBooleanTrue!,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrService as String: "hustles/\(userUID)",
kSecAttrAccount as String: userUID,
kSecValueData as String: passwordData
]
SecItemAdd(saveQuery as CFDictionary, nil)
Then your read function must be (inspired from this answer):
func retrievePrivateKey(for userUID: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrSynchronizable as String: kCFBooleanTrue!,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrService as String: "hustles/\(userUID)",
kSecAttrAccount as String: userUID,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
return nil
}
if let keyData = item as? Data,
let keyString = String(data: keyData, encoding: .utf8) {
return keyString
}
return nil
}
The kSecMatchLimit as String: kSecMatchLimitOne
makes sure only one item is returned (which makes sense for our use-case, since each UID should only have one private key associated with it).
That ensures your search criteria in the read function matches the attributes set when the item was saved, so you can successfully retrieve it from the keychain.