Хорошо, я нашел решение в этом сообщении в блоге Двойной элемент цепочки для ключей при добавлении пароля
Подводя итог, можно сказать, что проблема в том, что пример приложения GenericKeychain использует значение, хранящееся в ключе kSecAttrGeneric, в качестве идентификатора для элемента цепочки для ключей, хотя фактически это не то, что API использует для определения уникального элемента цепочки для ключей. Ключами, которые необходимо установить с уникальными значениями, являются ключ kSecAttrAccount и / или ключ kSecAttrService.
Вы можете переписать инициализатор KeychainItemWrapper, так что вам не нужно изменять какой-либо другой код, изменив эти строки:
Изменение:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
до:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];
и изменить:
[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
до:
[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];
Или вы можете сделать то, что я сделал, и написать новый инициализатор, который принимает оба идентифицирующих ключа:
Редактировать: Для людей, использующих ARC (вы должны быть в настоящее время), отметьте nycynik's answer для всех правильных обозначений моста
- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
if (self = [super init])
{
NSAssert(account != nil || service != nil, @"Both account and service are nil. Must specifiy at least one.");
// Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
// kSecAttrService are used as unique identifiers differentiating keychain items from one another
genericPasswordQuery = [[NSMutableDictionary alloc] init];
[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
[genericPasswordQuery setObject:service forKey:(id)kSecAttrService];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
NSMutableDictionary *outDictionary = nil;
if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
//Adding the account and service identifiers to the keychain
[keychainItemData setObject:account forKey:(id)kSecAttrAccount];
[keychainItemData setObject:service forKey:(id)kSecAttrService];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
}
[outDictionary release];
}
return self;
}
Надеюсь, это поможет кому-то еще!