4

したがって、OS X キーチェーンには次の 3 つの情報があります。

  • ServiceName (私のアプリの名前)
  • ユーザー名
  • パスワード

私は明らかに ServiceName を常に知っています。その ServiceName の保存されたユーザー名を見つける方法はありますか? (ユーザー名がわかれば、パスワードを見つけるのは簡単です。)

これを行うには、 EMKeychainなどの優れた Cocoa ラッパーを使用することをお勧めします。しかし、EMKeychain では、キーチェーン アイテムを取得するために UserName が必要です。

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

資格情報を見つけるためにユーザー名が必要な場合、キーチェーンに保存されている資格情報をどのように完全に利用することが期待されていますか? ユーザー名を .plist ファイルなどに保存するのがベスト プラクティスですか?

4

3 に答える 3

6

SecKeychainFindGenericPassword単一のキーチェーンアイテムのみを返します。特定のサービスのすべての汎用パスワードを検索するには、キーチェーンでクエリを実行する必要があります。ターゲットとするOSXのバージョンに基づいて、これを行うにはいくつかの方法があります。

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 を使用しますが、それはそのクラスが課す人為的な区別です。基礎となる Keychain Services 関数は、キーチェーン アイテムを見つけるためにユーザー名を必要としません。

SecKeychainFindGenericPassword直接使用する場合は、ユーザー名パラメーターに0andを渡します。そのサービスに存在するキーチェーン アイテムをNULL返します。

ただし、それは 1 つの項目のみを返します。ユーザーが同じサービスで複数のキーチェーン アイテムを持っている場合、それが何か、またはどのアイテムを取得したかはわかりません (ドキュメントには、「最初」と見なされるものを指定せずに、「最初に」一致するアイテムが返されると記載されています)。そのサービスのすべてのアイテムが必要な場合は、検索を作成して使用する必要があります。

于 2011-12-09T21:31:48.493 に答える
0

汎用パスワードには、サービス名とユーザー名の一意のキーがあります。したがって、単一の汎用キーチェーン エントリを取得するには、両方を提供する必要があります。ただし、SecKeychainFindGenericPassword関数を使用して、特定のサービスのすべての汎用キーチェーン エントリを反復処理できます。

(免責事項: EMKeychain でこれを行うことについては何も知りません。)

于 2011-12-09T21:17:06.300 に答える