将数据保存在Swift 3中只能通过Touch ID访问的Keychain中

我正在努力实现以下代码的和平:

  • 将一些数据存储在钥匙串中。
  • 只有用户通过Touch ID或Pass Codevalidation才能获取数据。

我观看了Keychain和Touch ID身份validation,并了解以下内容:

如果您在添加一个新的值到Keychain时设置了正确的参数,下一次您将尝试将其取出,系统将自动显示Touch IDpopup窗口。

我写了一些代码,我的假设不起作用。 这是我写的:

// // Secret value to store // let valueData = "The Top Secret Message V1".data(using: .utf8)!; // // Create the Access Controll object telling how the new value // should be stored. Force Touch ID by the system on Read. // let sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, nil); // // Create the Key Value array, that holds the query to store // our data // let insert_query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrAccessControl: sacObject!, kSecValueData: valueData, kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow, // This two valuse ideifieis the entry, together they become the // primary key in the Database kSecAttrService: "app_name", kSecAttrAccount: "first_name" ]; // // Execute the query to add our data to Keychain // let resultCode = SecItemAdd(insert_query as CFDictionary, nil); 

起初我以为模拟器有一些问题,但没有,我能够检查是否触摸ID是否存在与以下代码:

  // // Check if the device the code is running on is capapble of // finger printing. // let dose_it_can = LAContext() .canEvaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, error: nil); if(dose_it_can) { print("Yes it can"); } else { print("No it can't"); } 

而且我还能够以编程方式显示以下代码的Touch IDpopup窗口:

  // // Show the Touch ID dialog to check if we can get a print from // the user // LAContext().evaluatePolicy( LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Such important reason ;)", reply: { (status: Bool, evaluationError: Error?) -> Void in if(status) { print("OK"); } else { print("Not OK"); } }); 

总结一下

触摸身份证的作品,但保存一个价值的钥匙串与强制触摸ID系统本身的标志是行不通的 – 我错过了什么?

苹果的例子

Apple提供的名为KeychainTouchID的示例:将Touch ID与Keychain以及LocalAuthentication一起使用也会显示不一致的结果,Touch ID不会被系统强制执行。

技术规格

  • Xcode 8.1
  • Swift 3

只有在背景队列上调用SecItemCopyMatching() ,才会显示Touch IDpopup窗口。 这在Keychain的PDF演示文稿和使用Touch ID进行身份validation的页面上显示 :

读一个秘密

 dispatch_async(dispatch_get_global_queue(...), ^(void){ CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &dataTypeRef); }); 

否则,你正在阻塞主线程,popup窗口不会出现。 SecItemCopyMatching()然后失败(超时后)与错误代码-25293 = errSecAuthFailed

例如,你的示例项目中的失败不是很明显,因为它在错误情况下输出了错误的variables

 if(status != noErr) { print("SELECT Error: \(resultCode)."); // <-- Should be `status` } 

和更新和删除类​​似。

这里是您的示例代码的组成版本,必要时派发到后台队列以检索钥匙串项目。 (当然,UI更新必须分派回主队列。)

它在我的testing中按照预期在具有Touch ID的iPhone上工作:出现Touch IDpopup窗口,并且钥匙串项目仅在成功validation后才被检索。

触摸身份validation在iOS模拟器上不起作用。

 override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // This two values identify the entry, together they become the // primary key in the database let myAttrService = "app_name" let myAttrAccount = "first_name" // DELETE keychain item (if present from previous run) let delete_query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrService: myAttrService, kSecAttrAccount: myAttrAccount, kSecReturnData: false ] let delete_status = SecItemDelete(delete_query) if delete_status == errSecSuccess { print("Deleted successfully.") } else if delete_status == errSecItemNotFound { print("Nothing to delete.") } else { print("DELETE Error: \(delete_status).") } // INSERT keychain item let valueData = "The Top Secret Message V1".data(using: .utf8)! let sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil)! let insert_query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrAccessControl: sacObject, kSecValueData: valueData, kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow, kSecAttrService: myAttrService, kSecAttrAccount: myAttrAccount ] let insert_status = SecItemAdd(insert_query as CFDictionary, nil) if insert_status == errSecSuccess { print("Inserted successfully.") } else { print("INSERT Error: \(insert_status).") } DispatchQueue.global().async { // RETRIEVE keychain item let select_query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrService: myAttrService, kSecAttrAccount: myAttrAccount, kSecReturnData: true, kSecUseOperationPrompt: "Authenticate to access secret message" ] var extractedData: CFTypeRef? let select_status = SecItemCopyMatching(select_query, &extractedData) if select_status == errSecSuccess { if let retrievedData = extractedData as? Data, let secretMessage = String(data: retrievedData, encoding: .utf8) { print("Secret message: \(secretMessage)") // UI updates must be dispatched back to the main thread. DispatchQueue.main.async { self.messageLabel.text = secretMessage } } else { print("Invalid data") } } else if select_status == errSecUserCanceled { print("User canceled the operation.") } else { print("SELECT Error: \(select_status).") } } }