我正在尝试使用此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()的调用失败。经过一些测试后,似乎无法使用“Password”和“Account Number”以外的其他值作为标识符。

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

最佳答案

好的,我在此博客文章Keychain duplicate item when adding password中找到了解决方案

综上所述,问题在于GenericKeychain示例应用程序使用存储在kSecAttrGeneric key 中的值作为 key 链项目的标识符,而实际上这并不是API用于确定唯一 key 链项目的标识符。您需要设置唯一值的键是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;
}

希望这可以帮助其他人!

10-07 23:48