我正在尝试使用此Apple示例代码中提供的KeychainWrapper类:https://developer.apple.com/library/content/samplecode/GenericKeychain/
在示例应用程序中,该类的init方法以:
开头- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
if (self = [super init])
{
// Begin Keychain search setup. The genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
genericPasswordQuery = [[NSMutableDictionary alloc] init];
[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
在示例应用程序中,它使用两个值作为标识符字符串。 “密码”和“帐号”。在我的代码中实现类时,我使用了一些自定义标识符,但代码不起作用。对SecItemAdd()的调用失败。经过一些测试后,似乎使用“密码”和“帐号”以外的值作为标识符不起作用。
是否有人知道允许的值和/或是否可以为您的钥匙串项目设置自定义标识符?
答案 0 :(得分:61)
好的,我在这篇博文Keychain duplicate item when adding password
中找到了解决方案总而言之,问题在于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;
}
希望这可以帮助其他人!
答案 1 :(得分:10)
与上述相同,但适用于ARC。谢谢西蒙
- (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:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[genericPasswordQuery setObject:account forKey:(__bridge id)kSecAttrAccount];
[genericPasswordQuery setObject:service forKey:(__bridge 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:(__bridge id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
CFMutableDictionaryRef outDictionary = NULL;
if (! SecItemCopyMatching((__bridge 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:(__bridge id)kSecAttrAccount];
[keychainItemData setObject:service forKey:(__bridge 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:(__bridge id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary];
}
if(outDictionary) CFRelease(outDictionary);
}
return self;
}
答案 2 :(得分:1)
Simon几乎解决了我的问题,因为在更改KeychainItemWrapper.m之后,我遇到了从钥匙串获取数据和设置数据的问题。 所以在将它添加到KeychainItemWrapper.m之后,我用它来获取和存储项目:
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];
[keychainItem setObject:@"some value" forKey:(__bridge id)kSecAttrGeneric];
NSString *value = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];
因为[keychainItem objectForKey: (__bridge id)kSecAttrService]
正在返回帐户(在此示例中为@"Identifier"
)这是有意义的,但我花了一些时间才意识到我需要使用kSecAttrGeneric从包装器中获取数据。