I was basically saving items into the Keychain with the following query dictionary:
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: value,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
Where key
is a String
value and value
is a Data
that used to be a String
.
I was getting the following error:
- code: -25299
- description: The specified item already exists in the keychain
After a lot of digging in I saw that I needed to add kSecAttrService
to the dictionary and after that it all started working. The service
value is a String
value.
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecValueData as String: value,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
These were the articles that suggested adding the kSecAttrService
parameter:
But in the same code base I found that other developers were saving using a dictionary similar to the one I first provided and it works:
var query: [String : Any] = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data
]
I don't know how to explain why my first implementation didn't work even though it was similar to what was already in the code base but the second approach worked well.
Regardless of the query dictionary, this is how I'm saving things:
static func save(value: Data, key: String, service: String) -> KeyChainOperationStatus {
logInfo("Save Value - started, key: \(key), service: \(service)")
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key,
kSecValueData as String: value,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
// Remove any existing key
let cleanUpStatus = SecItemDelete(query as CFDictionary)
let cleanUpStatusDescription = SecCopyErrorMessageString(cleanUpStatus, nil)?.asString ?? "__cleanup_status_unavailable"
logInfo("Save Value - cleanup status: \(cleanUpStatus), description: \(cleanUpStatusDescription)")
guard cleanUpStatus == errSecSuccess || cleanUpStatus == errSecItemNotFound else {
logError("Save Value - Failed cleaning up KeyChain")
return .cleanupFailed(code: cleanUpStatus)
}
// Add the new key
let saveStatus = SecItemAdd(query as CFDictionary, nil)
let saveStatusDescription = SecCopyErrorMessageString(saveStatus, nil)?.asString ?? "__save_status_unavailable"
logInfo("Save Value - save status [\(saveStatus)] : \(saveStatusDescription)")
guard saveStatus == errSecSuccess else {
logError("Save Value - Failed saving new value into KeyChain")
return .savingFailed(code: saveStatus)
}
return .successs
}