获取存储在Keychain中的用户名,只使用ServiceName?或者:你应该在哪里存储用户名? [英] Get the Username(s) stored in Keychain, using only the ServiceName? OR: Where are you supposed to store the Username?

查看:186
本文介绍了获取存储在Keychain中的用户名,只使用ServiceName?或者:你应该在哪里存储用户名?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以OS X Keychain有三个信息:




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

  • 用户名

  • 密码



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



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



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

如果您需要使用用户名来查找凭证,您希望如何充分利用保存凭证的密钥链?

SecKeychainFindGenericPassword code>只返回单个钥匙串项。要查找特定服务的所有通用口令,您需要在钥匙串上运行查询。有几种方法可以根据您指定的OS X版本来执行此操作。



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

   - (NSDictionary *)genericPasswordsWithService: *)service {
OSStatus状态;

//构造一个查询。
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;
}

//枚举结果。
NSMutableDictionary * result = [NSMutableDictionary dictionary];
while(1){
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search,& item);
if(status)
break;

//查找account属性和密码值。
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及更高版本,可以使用稍微不方便的 SecItemCopyMatching API:

   - (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([keychain genericPasswords]中的LKKCGenericPassword *项){
if([service isEqualToString:item.service]){
[result setObject:item.password forKey:item.account];
}
}
返回结果;
}

(我没有尝试运行,甚至编译任何上述代码样品;对任何拼写错误。)


So the OS X Keychain has three pieces of information:

  • ServiceName (the name of my app)
  • Username
  • Password

I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)

I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!

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

How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?

解决方案

SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.

If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.

- (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;
}

For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:

- (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;
}

For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.

- (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;
}

(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)

这篇关于获取存储在Keychain中的用户名,只使用ServiceName?或者:你应该在哪里存储用户名?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆