我使用Touch ID标志将密码保存到钥匙串:
+ (void)setPasscode:(NSString *)passcode
{
CFErrorRef error = NULL;
SecAccessControlRef sacObject;
sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error);
if(sacObject == NULL || error != NULL)
{
DLog(@"can't create sacObject: %@", error);
return;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
(__bridge id)kSecValueData: [passcode dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecUseNoAuthenticationUI: @YES,
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
});
}
检索它:
+ (void)getCurrentPasscodeWithSuccess:(void (^)(NSString *))success failure:(void (^)(OSStatus))failure
{
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecUseOperationPrompt: kOperationPrompt
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
if (status == errSecSuccess)
{
if (success) {
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
success(result);
}
} else {
if (failure) {
failure(status);
}
}
});
}
这很好用。但是,如果禁用Touch ID,并在设备上重新启用它,SecItemCopyMatching将返回OSStatus -25300(errSecItemNotFound)。 问题是该项目仍然存在(我认为)。因为当我尝试访问它时,会出现Touch ID提示。
我尝试使用以下方法检查项目是否存在:
+ (void)checkIfPasscodeExistsInKeychainWithCompletion:(void (^)(BOOL))completion
{
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
BOOL exists = status != errSecItemNotFound;
if (completion) {
completion(exists);
}
});
}
这会触发Touch ID提示,然后返回在提供触摸时不存在的错误。
但如果我删除(__bridge id)kSecClass:(__ bridge id)kSecClassGenericPassword行,我会得到它在钥匙串中的状态。
答案 0 :(得分:2)
您正在使用 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly 作为商品的Accessiblity常量。这意味着只有在设备上设置了密码时才能添加项目,并添加了TouchID。如果您将项目保存在钥匙串中后删除了设备密码(或在您的情况下为TouchID),则此项目将不再可用。
即使您重新添加设备密码(或TouchID),此项目也不再可用。您必须创建同一项目的副本。
有关kSecAttrAccessible常量的更多信息,请参阅文档:Keychain Item Accessibility Constants。
有关整个过程如何运作的信息,请参阅Apple's security whitepaper
答案 1 :(得分:1)
它看起来像是一个苹果虫,我不久前打开了rdar:// 24237713。
使用包含kSecMatchLimitAll以外的匹配限制的查询的SecItemCopyMatching将在设备密码打开后返回错误的结果&关闭。似乎不再可访问的旧项目不会从内部存储中删除,而只会标记为不可用,但它们仍然参与创建结果集。
测试项目以证明问题: https://github.com/mndgs/TestKeychainBug