iOS 钥匙串服务:只允许 kSecAttrGeneric Key 使用特定值?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4891562/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:47:01  来源:igfitidea点击:

iOS Keychain Services: only specific values allowed for kSecAttrGeneric Key?

ioskeychain

提问by Simon Goldeen

I am trying to use the KeychainWrapper class provided in this Apple sample code: https://developer.apple.com/library/content/samplecode/GenericKeychain/

我正在尝试使用此 Apple 示例代码中提供的 KeychainWrapper 类:https: //developer.apple.com/library/content/samplecode/GenericKeychain/

In the sample app, the class has this init method that starts as:

在示例应用程序中,该类具有以下 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];

In the sample app, it uses two values for the identifier string. "Password" and "Account Number". When implementing the class in my code, I used some custom identifiers and the code did not work. The call to SecItemAdd() failed. After some testing, it seems that using values other than "Password" and "Account Number" for the identifier does not work.

在示例应用程序中,它使用两个值作为标识符字符串。“密码”和“帐号”。在我的代码中实现该类时,我使用了一些自定义标识符,但代码不起作用。调用 SecItemAdd() 失败。经过一些测试,似乎对标识符使用“密码”和“帐号”以外的值不起作用。

Does anyone know what values are allowed and/or if it is possible to have custom identifiers for your keychain items?

有谁知道允许使用哪些值和/或是否可以为您的钥匙串项目提供自定义标识符?

回答by Simon Goldeen

Okay, I found the solution in this blog post Keychain duplicate item when adding password

好的,我在这篇博客文章中找到了添加密码时钥匙串重复项的解决方案

To sum it up, the issue is that the GenericKeychain sample app uses the value stored in kSecAttrGeneric key as the identifier for the keychain item when in fact that is not what the API uses to determine a unique keychain item. The keys you need to set with unique values are the kSecAttrAccount key and/or the kSecAttrService key.

总而言之,问题在于 GenericKeychain 示例应用程序使用存储在 kSecAttrGeneric 密钥中的值作为钥匙串项目的标识符,而实际上这不是 API 用于确定唯一钥匙串项目的内容。您需要使用唯一值设置的键是 kSecAttrAccount 键和/或 kSecAttrService 键。

You can rewrite the initilizer of KeychainItemWrapper so you don't need to change any other code by changing these lines:

您可以重写 KeychainItemWrapper 的初始化程序,这样您就不需要通过更改这些行来更改任何其他代码:

Change:

改变:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

to:

到:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

and change:

并改变:

[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];

to:

到:

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

Or, you could do what I did and write a new initilizer that takes both of the identifying keys:

或者,您可以按照我所做的来编写一个新的初始化程序,它采用两个识别键:

Edit: For people using ARC (you should be nowadays) check nycynik's answerfor all the correct bridging notation

编辑:对于使用 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;
}

Hope this helps someone else out!

希望这可以帮助别人了!

回答by nycynik

Same as above, but it works for ARC. Thanks simon

与上面相同,但它适用于 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;
}

回答by Zenuka

Simon almost fixed my issue because after changing the KeychainItemWrapper.m, I had issues getting and setting data to and from the keychain. So after adding this to the KeychainItemWrapper.m, I used this to get and store items:

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];

Because [keychainItem objectForKey: (__bridge id)kSecAttrService]is returning the Account (in this example @"Identifier") which makes sense but it took me some time before I realized I needed to use kSecAttrGeneric to fetch data from the wrapper.

因为[keychainItem objectForKey: (__bridge id)kSecAttrService]正在返回帐户(在本例中@"Identifier"),这是有道理的,但我花了一些时间才意识到我需要使用 kSecAttrGeneric 从包装器中获取数据。