4

所以 OS X 钥匙串有三个信息:

  • ServiceName(我的应用程序的名称)
  • 用户名
  • 密码

我显然总是知道 ServiceName。有没有办法找到该 ServiceName 的任何已保存用户名?(一旦知道了用户名,就很容易找到密码。)

我更喜欢使用一个不错的 Cocoa 包装器,例如EMKeychain来执行此操作。但是 EMKeychain 需要 UserName 才能获取任何钥匙串项!

+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;

如果您需要用户名来查找凭据,您希望如何充分利用钥匙串中的保存凭据?将用户名保存在 .plist 文件中的最佳做法是什么?

4

3 回答 3

6

SecKeychainFindGenericPassword只返回一个钥匙串项。要查找特定服务的所有通用密码,您需要在钥匙串上运行查询。有几种方法可以做到这一点,具体取决于您所针对的 OS X 版本。

如果您需要在 10.5 或更低版本上运行,则需要使用SecKeychainSearchCreateFromAttributes. 这是一个相当可怕的 API。这是一个粗略的方法,它返回将用户名映射到密码的字典。

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    OSStatus status;

    // Construct a query.
    const char *utf8Service = [service UTF8String];
    SecKeychainAttribute attr = { .tag = kSecServiceItemAttr, 
                                  .length = strlen(utf8Service), 
                                  .data = (void *)utf8Service };
    SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
    SecKeychainSearchRef *search = NULL;
    status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
    if (status) {
        report(status);
        return nil;
    }

    // Enumerate results.
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    while (1) {
        SecKeychainItemRef item = NULL;
        status = SecKeychainSearchCopyNext(search, &item);
        if (status)
            break;

        // Find 'account' attribute and password value.
        UInt32 tag = kSecAccountItemAttr;
        UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
        SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
        SecKeychainAttributeList *attrList = NULL;
        UInt32 length = 0;
        void *data = NULL;
        status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
        if (status) {
            CFRelease(item);
            continue;
        }

        NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, @"SecKeychainItemCopyAttributesAndData is messing with us");
        NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
        NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
        [result setObject:password forKey:account];

        SecKeychainItemFreeAttributesAndData(attrList, data);
        CFRelease(item);
    }
    CFRelease(search);
    return result;
}

对于 10.6 及更高版本,您可以使用不太方便的SecItemCopyMatchingAPI:

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
                           kSecClassGenericPassword, kSecClass,
                           (id)kCFBooleanTrue, kSecReturnData,
                           (id)kCFBooleanTrue, kSecReturnAttributes,
                           kSecMatchLimitAll, kSecMatchLimit,
                           service, kSecAttrService,
                           nil];
    NSArray *itemDicts = nil;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
    if (status) {
        report(status);
        return nil;
    }
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    for (NSDictionary *itemDict in itemDicts) {
        NSData *data = [itemDict objectForKey:kSecValueData];
        NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
        NSString *account = [itemDict objectForKey:kSecAttrAccount];
        [result setObject:password forKey:account];
    }
    [itemDicts release];
    return result;
}

对于 10.7 或更高版本,您可以使用我的精彩LKKeychain框架(PLUG!)。它不支持构建基于属性的查询,但您可以简单地列出所有密码并过滤掉您不需要的密码。

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    for (LKKCGenericPassword *item in [keychain genericPasswords]) {
        if ([service isEqualToString:item.service]) {
            [result setObject:item.password forKey:item.account];
        }
    }
    return result;
}

(我没有尝试运行,甚至没有编译任何上述代码示例;抱歉有任何错别字。)

于 2011-12-12T13:42:36.560 回答
2

您不需要用户名。您使用 EMKeychain,但这是该类强加的人为区别;底层钥匙串服务功能不需要用户名来查找钥匙串项。

直接使用时SecKeychainFindGenericPassword,传递0NULL为用户名参数。它将返回该服务上存在的钥匙串项。

但是,这将只返回一项。如果用户在同一个服务上有多个钥匙串项目,你将不知道,或者你得到了哪个(文档说它返回“第一个”匹配项目,没有说明它认为“第一个”是什么)。如果您想要该服务的任何和所有项目,您应该创建一个搜索并使用它。

于 2011-12-09T21:31:48.493 回答
0

通用密码具有服务名称和用户名的唯一键。因此,要获取单个通用钥匙串条目,您需要同时提供两者。SecKeychainFindGenericPassword但是,您可以使用该函数遍历给定服务的所有通用钥匙串条目。

(免责声明:我对在 EMKeychain 中执行此操作一无所知。)

于 2011-12-09T21:17:06.300 回答