Ios 如何在不调用生物特征识别的情况下检查现有钥匙链项目

Ios 如何在不调用生物特征识别的情况下检查现有钥匙链项目,ios,keychain,Ios,Keychain,我有一个在设备钥匙链中存储用户密码的应用程序,可以使用设备生物识别技术(面部ID或触摸ID)访问该应用程序 我通过以下几点成功地做到了这一点: const SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlUserPresence, &accessControlE

我有一个在设备钥匙链中存储用户密码的应用程序,可以使用设备生物识别技术(面部ID或触摸ID)访问该应用程序

我通过以下几点成功地做到了这一点:

const SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlUserPresence, &accessControlError);
LAContext * const localAuthContext = [[LAContext alloc] init];
NSDictionary * const addQuery = @{
   (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
   (__bridge NSString *)kSecAttrAccount: username,
   (__bridge NSString *)kSecAttrAccessControl: (__bridge id)accessControl,
   (__bridge NSString *)kSecUseAuthenticationContext: localAuthContext,
   (__bridge NSString *)kSecValueData: passwordData
};
const OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)addQuery, nil);
当我想更新密码时,问题就出现了。我需要使用
SecItemUpdate(…)
函数。因此,我执行了一项检查,以查看给定用户名下是否已经存在该项,但由于该项的存储方式,我的iPhone X上会出现Face ID提示符

NSDictionary * const findQuery = @{
   (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
   (__bridge NSString *)kSecAttrAccount: username,
   (__bridge NSString *)kSecReturnData: @(NO)
};
const OSStatus readStatus = SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, nil);

有没有办法在不调用生物识别访问的情况下实现这一点?如果没有,我如何才能可靠地检查我是否要添加或更新钥匙链项目?

我找不到一种可靠的方法来使用
SecItemUpdate(…)
,而不调用生物特征,因此我所采用的方法是删除一个预先存在的条目,然后添加一个新条目(有效地更新它)

此代码块添加新项目或替换现有项目

NSData * const passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
NSString * const itemClass = (__bridge NSString *)kSecClassGenericPassword;
NSString * const account = username;
NSString * const service = [[NSBundle mainBundle] bundleIdentifier];

// First, try to find an existing item
NSDictionary * const findQuery = @{
    (__bridge NSString *)kSecClass: itemClass,
    (__bridge NSString *)kSecAttrService: service,
    (__bridge NSString *)kSecAttrAccount: account,
    (__bridge NSString *)kSecUseAuthenticationUI: (__bridge NSString *)kSecUseAuthenticationUIFail,
};
const OSStatus readStatus = SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, nil);
NSLog (@"Tried to find existing secure local password. Status = %d", readStatus);
const BOOL passwordAlreadyExists = (readStatus == errSecInteractionNotAllowed);
if (passwordAlreadyExists) {
    // Delete
    NSDictionary * const deleteQuery = @{
        (__bridge NSString *)kSecClass: itemClass,
        (__bridge NSString *)kSecAttrService: service,
        (__bridge NSString *)kSecAttrAccount: account,
        (__bridge NSString *)kSecReturnData: @(NO)
    };
    const OSStatus deleteStatus = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
    NSLog (@"Deleted existing secure local password. Status = %d", deleteStatus);
}

// Create an access control instance that dictates how the item can be read later.
CFErrorRef accessControlError = nil;
const SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlUserPresence, &accessControlError);
if (accessControlError) {
    NSError * const error = (__bridge NSError *)accessControlError;
    NSLog (@"There was an error creating the access control: %@", error.localizedDescription);
    return;
}

// Create the context
LAContext * const localAuthContext = [[LAContext alloc] init];
localAuthContext.localizedFallbackTitle = @"Use Magic Link";

// Build the query
NSDictionary * const addQuery = @{
    (__bridge NSString *)kSecClass: itemClass,
    (__bridge NSString *)kSecAttrService: service,
    (__bridge NSString *)kSecAttrAccount: account,
    (__bridge NSString *)kSecAttrAccessControl: (__bridge id)accessControl,
    (__bridge NSString *)kSecUseAuthenticationContext: localAuthContext,
    (__bridge NSString *)kSecValueData: passwordData
};

// Execute
const OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)addQuery, nil);
NSLog (@"Created secure local password. Status = %d", addStatus);

我用的是下面这样的东西

    func has(service: String, account: String, options: [Options] = []) -> Bool {
        var query = self.query(service: service, account: account, options: options)
        query[kSecUseAuthenticationContext as String] = internalContext

        var secItemResult: CFTypeRef?
        status = SecItemCopyMatching(query as CFDictionary, &secItemResult)

        return status == errSecSuccess || status == errSecInteractionNotAllowed
    }

    private var internalContext: LAContext? = {
        let context = LAContext()
        context.interactionNotAllowed = true
        return context
    }()

以前,您也可以使用UseAuthenticationUIFail,但从iOS 14开始就不推荐使用它。

为什么不在UserDefaults中存储一个布尔值来指示是否存在密码?@Paulw11谢谢我考虑过这一点,但是希望有更可靠的东西。您误解了使用
kSecReturnData
——如果您想返回数据类型,这是正确的
kSecReturnAttributes
返回字典等是真的@Ricky你有没有找到解决方法?我在你的例子中也有同样的问题,
self.query(…)
看起来像什么?