secItemCopyMatching返回空数据。

9
首先,我看了WWDC 2013有关使用keychain保护密码的会议。我想做一个基本的密码存储。我看了整个视频,但在前10分钟就找到了我需要的内容。看起来很简单,但我并不完全明白数据编码和检索如何工作。
问题:在secItemCopyMatching之后,我检查我的NSData对象,以确保它不为空,然后才将其转换为NSString。问题是,它总是为空。下面是我保存keychain条目或更新它的方法,以及我检索它的方法。非常感谢任何帮助和解释。
更新(已编辑):Fruity Geek,感谢您的回复。我使用__bridge更新了代码。现在,我的问题归结为,我是否正确存储和检索密码?我两个都错了还是只有一个错了?我的NSData实例总是为空。我正在检查返回代码,并且我的SecItemAdd和SecItemUpdate(当keychaing条目存在时)都工作正确。我似乎无法检索存储的字符串值(passcode),以将其与用户输入的passcode进行比较。感谢各位的帮助。以下是我现在正在做的:
更新#2:(使用Fruity Geek的回答和最终工作版本进行编辑。我的编辑仅包括对下面代码的更改。)
设置keychain条目:
NSData *secret = [_backupPassword dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
    (__bridge id)kSecAttrService: twServiceName,
    (__bridge id)kSecAttrAccount: twAccountName,
    (__bridge id)kSecValueData: secret,
};
OSStatus status =
    SecItemAdd((__bridge CFDictionaryRef)query, NULL);

if (status == errSecDuplicateItem) {
    // this item exists in the keychain already, update it
    query = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
        (__bridge id)kSecAttrService: twServiceName,
        (__bridge id)kSecAttrAccount: twAccountName,
    };
    NSDictionary *changes = @{
        (__bridge id)kSecValueData: secret,
    };
    status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
}

从钥匙串中获取密码:

NSDictionary *query = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
    (__bridge id)kSecAttrService: twServiceName,
    (__bridge id)kSecAttrAccount: twAccountName,
    (__bridge id)kSecReturnData: @YES,
};
NSData *data = NULL;
CFTypeRef dataTypeRef = (__bridge CFTypeRef)data;
OSStatus status =
    SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);

NSData *data = (__bridge NSData *)dataTypeRef;

NSString *passcode = @"none";
if (status == errSecSuccess) {
    // we found a keychain entry, set the passcode
    if (data)
        passcode = [NSString stringWithUTF8String:[data bytes]];
}

twServiceName和twAccountName是静态NSStrings。

正如我所说,我对__bridge或CFTypeRef不太了解。我查看了苹果文档、这里和其他站点的许多帖子,但关键链和这些术语对我来说都是全新的,我还在努力弄清楚。希望这里的某个人能指出我的错误并帮助我理解。提前感谢您的帮助。

iOS 7 / Xcode 5

1个回答

12

你不拥有任何核心基础对象(你没有创建或复制它们),因此你不想保留或释放它们,因此使用CFBridgingReleaseCFBridgingRetain是不正确的。当你想要将其转换为Objective-C对象时,请改用 (__bridge id)

(__bridge id)kSecAttrService

何时应该使用__bridge和CFBridgingRelease/CFBridgingRetain?

你的data变量和dataTypeRef是两个不同的指针。只有dataTypeRef在SecItemCopyMatching中填充了数据。在它被SecItemCopyMatching填充后,将CFTypeRef转换为NSData,以便你的数据不总是为空。

CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
NSData *data = (__bridge NSData *)dataTypeRef;

您应该仔细查看所有SecItem函数调用返回的OSStatus。有许多可能不成功的返回代码。在您的情况下,您检测到SecItemAdd中的重复项,然后将其更新为完全相同的项(什么也不做)。相反,您应该首先尝试使用SecItemCopyMatching检索它。如果没有找到匹配项,请使用SecItemAdd。如果找到匹配项,请使用SecItemUpdate。

来自Apple的示例代码非常糟糕,没有写ARC且令人困惑,但它确实存在。特别是,writeToKeychain方法是您需要的。https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/iPhoneTasks/iPhoneTasks.html#//apple_ref/doc/uid/TP30000897-CH208-SW1


谢谢。这有助于我理解 __bridge。我正在检查返回代码。我想尝试添加会实现与 secItemCopyMatching 相同的结果。如果条目不存在,则添加将起作用,否则我会收到 errSecDuplicateItem,然后我会使用密码更新该条目。 - knarf
之前我编辑完帖子后按了回车,结果没意识到它会提交我的帖子,速度太慢了... 我的问题仍然存在,我是否正确编码和存储密码,以及应该如何检索它?在我存储它之后,我尝试使用CopyMatching并将密码取回,但我的NSData实例始终为空。让我在帖子中更新我的代码... - knarf
糟糕!现在我明白了。感谢您的帮助!但愿我能为您的答案点赞。 - knarf
顺便说一句,我认为你的意思很明显,但是你可能需要更正最后一个答案中的小错别字以避免任何混淆(data -> dataTypeRef): NSData *data = (__bridge NSData *)dataTypeRef; - knarf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接