钥匙串:报告为errSecItemNotFound的项目,但在添加时收到errSecDuplicateItem

这个问题一直在困扰我,我希望有人对此有所了解。 从本质上讲,我有一小部分用户无法保存/更新项目到钥匙串。 有问题的控制stream程如下:

  1. 我们使用SecItemCopyMatching检查项目的存在。 这将返回错误代码errSecItemNotFound

  2. 然后我们尝试通过SecItemAdd添加项目,但是这会返回errSecDuplicateItem

因此,我们有一些用户根本无法更新钥匙串项目的一个子集,要求他们恢复他们的设备以清除钥匙串。 这显然是一个不可接受的解决方法。 它似乎以前为他们工作,但现在已经进入这个不可更新的周期。

经过研究,我看到有关SecItemCopyMatching使用的search查询的问题不够具体,但我的代码尽可能使用通用search查询。

 + (NSMutableDictionary*)queryForUser:(NSString*)user key:(NSString*)key { if (!key || !user) { return nil; } NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier]; NSString* prefixedKey = [NSString stringWithFormat:@"%@.%@", bundleId, key]; NSMutableDictionary* query = [NSMutableDictionary dictionary]; [query addEntriesFromDictionary:@{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword}]; [query addEntriesFromDictionary:@{(__bridge id)kSecAttrAccount : user}]; [query addEntriesFromDictionary:@{(__bridge id)kSecAttrService : prefixedKey}]; [query addEntriesFromDictionary:@{(__bridge id)kSecAttrLabel : prefixedKey}]; [query addEntriesFromDictionary:@{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly}]; return query; } 

执行更新/添加的代码如下(对于冗长的抱歉):

 // Setup the search query, to return the *attributes* of the found item (for use in SecItemUpdate) NSMutableDictionary* query = [self queryForUser:username key:key]; [query addEntriesFromDictionary:@{(__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue}]; // Prep the dictionary we'll use to update/add the new value NSDictionary* updateValues = @{(__bridge id) kSecValueData : [value dataUsingEncoding:NSUTF8StringEncoding]}; // Copy what we (may) already have CFDictionaryRef resultData = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&resultData); // If it already exists, update it if (status == noErr) { // Create a new query with the found attributes NSMutableDictionary* updateQuery = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary*)resultData]; [updateQuery addEntriesFromDictionary:@{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword}]; // Update the item in the keychain status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateValues); if (status != noErr) { // Update failed, I've not seen this case occur as of yet } } else { // Add the value we want as part of our original search query, and add it to the keychain [query addEntriesFromDictionary:updateValues]; [query removeObjectForKey:(__bridge id)kSecReturnAttributes]; status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); if (status != noErr) { // Addition failed, this is where I'm seeing errSecDuplicateItem } } 

我们尝试使用SecItemDelete而不是检查/更新,但是这也返回了errSecItemNotFoundSecItemAdd失败之后。 删除代码是:

 + (BOOL)deleteItemForUser:(NSString *)username withKey:(NSString *)itemKey { if (!username || !itemKey) { return NO; } NSString * bundleId = [[NSBundle mainBundle] bundleIdentifier]; NSString * prefixedItemKey = [NSString stringWithFormat:@"%@.%@", bundleId, itemKey]; NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword, kSecClass, username, kSecAttrAccount, prefixedItemKey, kSecAttrService, nil]; OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); if (status != noErr) { // Failed deletion, returning errSecItemNotFound } return (status == noErr); } 

虽然我们为应用程序定义了2个钥匙串访问组,但受影响的钥匙串项目没有将访问组指定为属性(文档中指的是为所有访问组执行search)。 我还没有看到除errSecItemNotFounderrSecDuplicateItem以外的任何其他错误代码。

事实上,只有一小部分用户进入这种状况真的让我感到困惑。 是否有任何其他的考虑,我需要考虑到可能导致这一点的钥匙串,关于multithreading,冲洗,背景访问等…?

非常感谢。 我宁愿坚持使用Keychain Services API而不是使用第三方库。 我想了解这里的根本问题。

kSecClassGenericPassword的唯一密钥由以下组成:

 kSecAttrAccount kSecAttrService 

要检查它的存在,只用这些属性(包括kSecReturnAttributes标志)查询钥匙串存储。

包括kSecAttrLabelkSecAttrAccessible将排除具有相同唯一键但具有不同属性的任何现有项目。

一旦确认了其(非)存在,请添加其他属性并添加或更新。