iOS钥匙串服务:只允许kSecAttrGeneric钥匙的特定值?
我正在尝试使用此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];
在示例应用程序中,它使用标识符string的两个值。 “密码”和“帐号”。 在我的代码中实现类时,我使用了一些自定义标识符,代码无效。 对SecItemAdd()的调用失败。 经过一些testing,似乎使用“密码”和“帐号”以外的值作为标识符不起作用。
有谁知道允许哪些值和/或是否可以为您的钥匙链项目定制标识符?
好的,我在添encryption码的时候在这个博客中发现了Keychain重复项目的解决scheme
综上所述,问题是GenericKeychain示例应用程序使用存储在kSecAttrGeneric键中的值作为Keychain项目的标识符,实际上这不是API用于确定唯一Keychain项目的标识符。 您需要使用唯一值设置的密钥是kSecAttrAccount密钥和/或kSecAttrService密钥。
你可以重写KeychainItemWrapper的initilizer,所以你不需要通过改变这些行来改变任何其他的代码:
更改:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
至:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];
并改变:
[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
至:
[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];
或者,你可以做我所做的事情,并写一个新的消费者身份validation密钥:
编辑:对于使用ARC的人(你应该现在)检查nycynik的所有正确的桥接符号的答案
- (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; }
希望这可以帮助别人!
同上,但它适用于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; }
西蒙几乎解决了我的问题,因为在更改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]
正在返回Account(在本例中为@"Identifier"
),这是有道理的,但是我花了一些时间才意识到需要使用kSecAttrGeneric从包装器中获取数据。