APP安全机制(十九) —— 基于SwiftUI App的钥匙串服务和生物识别(一)

版本记录

版本号 时间
V1.0 2020.09.07 星期一

前言

在这个信息爆炸的年代,特别是一些敏感的行业,比如金融业和银行卡相关等等,这都对app的安全机制有更高的需求,很多大公司都有安全 部门,用于检测自己产品的安全性,但是及时是这样,安全问题仍然被不断曝出,接下来几篇我们主要说一下app的安全机制。感兴趣的看我上面几篇。
1. APP安全机制(一)—— 几种和安全性有关的情况
2. APP安全机制(二)—— 使用Reveal查看任意APP的UI
3. APP安全机制(三)—— Base64加密
4. APP安全机制(四)—— MD5加密
5. APP安全机制(五)—— 对称加密
6. APP安全机制(六)—— 非对称加密
7. APP安全机制(七)—— SHA加密
8. APP安全机制(八)—— 偏好设置的加密存储
9. APP安全机制(九)—— 基本iOS安全之钥匙链和哈希(一)
10. APP安全机制(十)—— 基本iOS安全之钥匙链和哈希(二)
11. APP安全机制(十一)—— 密码工具:提高用户安全性和体验(一)
12. APP安全机制(十二)—— 密码工具:提高用户安全性和体验(二)
13. APP安全机制(十三)—— 密码工具:提高用户安全性和体验(三)
14. APP安全机制(十四) —— Keychain Services API使用简单示例(一)
15. APP安全机制(十五) —— Keychain Services API使用简单示例(二)
16. APP安全机制(十六) —— Keychain Services API使用简单示例(三)
17. APP安全机制(十七) —— 阻止使用SSL Pinning 和 Alamofire的中间人攻击(一)
18. APP安全机制(十八) —— 阻止使用SSL Pinning 和 Alamofire的中间人攻击(二)

开始

首先看下主要内容:

了解如何将钥匙串服务和生物特征认证集成到简单的受密码保护的note-taking SwiftUI应用中。本文内容来自翻译

下面看下写作环境:

Swift 5, iOS 13, Xcode 11

Apple的钥匙串服务(Keychain Services )是一种机制,用于以安全和受保护的方式存储小的敏感数据,例如密码,加密密钥或用户令牌(token)。 使用钥匙串服务,您可以检查用户输入的密码是否与他们存储的密码相匹配,而不会使数据受到威胁。 但是,输入密码很繁琐! 为了解决此问题,Apple向许多设备添加了生物特征认证(biometric authentication)。 生物特征认证允许用户使用指纹或面部扫描快速安全地确认其身份。

在本教程中,您将学习如何将钥匙串服务和生物特征认证(Keychain Services and biometric authentication)集成到一个简单的受密码保护的note-taking SwiftUI应用中。

打开启动项目。 构建并运行。 首次运行时,应用程序会提示您输入密码以保护您的笔记。 继续,在两个field中键入密码,然后点击Set Password。 您会看到一个简单的记笔记应用程序,该应用程序可让您输入文本并稍后返回。

注意:如果您忘记密码,请不要担心-运行调试版本时,会有一个神奇的按钮可将应用程序重置为初始状态。 您会丢失秘密笔记,但不会被卡住。

该应用程序包装了一个UITextView来管理笔记。 在编辑器上方,您会看到三个按钮。 最左侧是垃圾桶按钮,它是上述的重置按钮。 右侧是一个用于更改密码的按钮,另一个是用于锁定和解锁笔记的按钮。

由于您刚刚输入了新密码,因此便笺已解锁并可以编辑。 在文本编辑器中键入一些文本。

现在停止并重新启动应用程序。该应用程序以锁定的笔记开始。编辑器窗口模糊以使内容模糊。点按锁定按钮,然后输入您先前设置的密码以解锁笔记。该应用使用User Defaults存储您的笔记和密码。

这使得对设备进行物理访问的攻击者可以轻松找到密码!钥匙串服务(Keychain Services)提供了一个更安全的地方来存储密码。在下一部分中,您将开始更新应用程序以执行此操作。


A Look at Keychain Services

钥匙串服务(Keychain Services)提供了由操作系统管理的加密数据库。它用于存储密码,加密密钥,证书或任何短数据。

要将某些内容存储在钥匙串中,请打包有关数据的几个属性以及机密信息。您将它们全部存储到钥匙串项目(keychain item)中。钥匙串服务为不同类型的item提供类:

  • kSecClassInternetPassword存储互联网站点的密码。
  • kSecClassGenericPassword存储任何类型的密码。
  • kSecClassCertificate存储证书。
  • kSecClassKey存储一个加密密钥项。
  • kSecClassIdentity存储identity

每个类使用不同的属性集。这些属性定义标识受保护项目所需的信息。它们还控制对机密信息的访问。您可以使用属性在以后搜索项目。如果需要,您可以在应用之间共享钥匙串。

Keychain Services API已经存在了很长时间。 这意味着它是一个行之有效,可靠和安全的信息存储场所。 不幸的是,这也意味着您将要处理为C编写的API

但是不要惊慌。 您将编写封装函数,以更现代的方式处理API。


Enabling Your Keychain

现在是时候设置您的钥匙串了。 您将增强该应用程序,以安全地添加,检索,更新和删除密码!

1. Adding a Password to the Keychain

在入门项目中,打开Models组中的KeychainServices.swift。 您将看到KeychainWrapperError的定义,这是一个自定义Error,将用于向用户提供反馈。

您要添加的第一件事是KeychainWrapper的初始定义。 在KeychainWrapperError之后,在文件末尾插入以下代码:

class KeychainWrapper {
  func storeGenericPasswordFor(
    account: String,
    service: String,
    password: String
  ) throws {
    guard let passwordData = password.data(using: .utf8) else {
      print("Error converting value to data.")
      throw KeychainWrapperError(type: .badData)
    }
  }
}

您必须首先将密码从String转换为Data。 如果转换失败,则会抛出error

注意:添加代码时,您会看到有关“已定义但从未使用过”常量的警告:

不用担心-您将在本教程的后面部分解决这些警告。 您现在可以忽略它们。

通过query可以访问钥匙串服务Keychain Service。 访问“钥匙串服务”的第一步是创建一个add query。 顾名思义,add query定义了要存储在钥匙串中的数据。

将以下代码添加到storeGenericPasswordFor(account:service:password :)的末尾:

// 1
let query: [String: Any] = [
  // 2
  kSecClass as String: kSecClassGenericPassword,
  // 3
  kSecAttrAccount as String: account,
  // 4
  kSecAttrService as String: service,
  // 5
  kSecValueData as String: passwordData
]

这是正在发生的事情:

  • 1) 该查询是一个字典,根据属性将String映射到Any对象。从Swift调用基于CAPI时,这种模式很常见。对于每个属性,您都提供以kSec开头的已定义全局常量。在每种情况下,都将常量转换为String(实际上是CFString),然后在该属性的值之后跟随常量。
  • 2) 第一键使用预定义的常数kSecClassGenericPassword将此项的类定义为通用密码。
  • 3) 对于通用密码项目,您提供一个帐户,即您的用户名字段。您将此作为参数传递给了方法。
  • 4) 接下来,为密码设置服务。这是一个任意字符串,应反映密码的用途,例如“用户登录”。您还将此作为参数传递给了方法。
  • 5) 最后,使用从传递给方法的字符串转换的passwordData设置项目的数据。

建立查询query后,就可以存储值了。在查询定义之后添加以下代码:

// 1
let status = SecItemAdd(query as CFDictionary, nil)
// 2
switch status {
// 3
case errSecSuccess:
  break
// 4
default:
  throw KeychainWrapperError(status: status, type: .servicesError)
}

这是此代码的作用:

  • 1) SecItemAdd(_:_ :)要求钥匙串服务向钥匙串添加信息。您将query转换为预期的CFDictionary类型。 C API通常使用返回值来显示函数的结果。在此,值的类型为OSStatus
  • 2) 您打开状态代码的各种值。在只检查一个值但谁知道将来会发生什么的情况下使用开关似乎有点奇怪。
  • 3) errSecSuccess表示您的密码现在在钥匙串中。您的工作已经完成!
  • 4) 如果status包含另一个值,则函数失败。 KeychainWrapperError包含一个初始化程序,该初始化程序使用SecCopyErrorMessageString(_:_ :)为该异常创建人类可读的消息。

您使用相同的模式来访问所有钥匙串功能:首先,创建一个查询来定义要执行的工作,然后使用该查询调用一个函数。

现在,您有了一种将密码存储在钥匙串中的方法。接下来,您将添加search功能以查找和检索刚刚添加的项目。

2. Searching for Keychain Items

从钥匙串读取项目的步骤与添加项目的步骤相同。将以下新方法添加到KeychainWrapper类的末尾:

func getGenericPasswordFor(account: String, service: String) throws -> String {
  let query: [String: Any] = [
    // 1
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecAttrService as String: service,
    // 2
    kSecMatchLimit as String: kSecMatchLimitOne,
    // 3
    kSecReturnAttributes as String: true,
    kSecReturnData as String: true
  ]
}

再次,从钥匙串读取项目的第一步是创建适当的查询:

  • 1) 在将密码添加到钥匙串时,您提供了kSecClass,kSecAttrAccountkSecAttrService。 现在,您可以使用这些值在钥匙串中找到项目。
  • 2) 您可以使用kSecMatchLimit告诉钥匙串服务您希望将单个项目作为搜索结果。
  • 3) 字典中的最后两个参数告诉Keychain Services返回找到的值的所有数据和属性。

query之后,添加以下代码:

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else {
  throw KeychainWrapperError(type: .itemNotFound)
}
guard status == errSecSuccess else {
  throw KeychainWrapperError(status: status, type: .servicesError)
}

首先,定义一个可选的CFTypeRef变量,以保存Keychain Services希望找到的值。 然后,您调用SecItemCopyMatching(_:_ :)。 您提供查询和指向目标值的指针。 此函数在钥匙串中搜索匹配项并将匹配项复制到项目item

注意:这可能是您以前从未见过的模式。 参数前的与号(&)表示它是指向内存插槽的指针,而不是值本身。 C函数将使用新值更新该位置的内存。

同样,状态代码提供错误信息。 当钥匙串服务找不到所需的物品时,此代码包含特定的错误。

现在,您有了钥匙串项,但是作为CFTypeRef。 在getGenericPasswordFor(account:service :)的末尾添加以下代码:

// 1
guard 
  let existingItem = item as? [String: Any],
  // 2
  let valueData = existingItem[kSecValueData as String] as? Data,
  // 3
  let value = String(data: valueData, encoding: .utf8)
  else {
    // 4
    throw KeychainWrapperError(type: .unableToConvertToString)
}

// 5
return value

这是正在发生的事情:

  • 1) 将返回的CFTypeRef强制转换为字典。
  • 2) 在字典中提取kSecValueData值并将其转换为Data
  • 3) 尝试将数据转换回字符串,这与您存储密码时所做的操作相反。
  • 4) 如果这些步骤中的任何一个返回nil,则表示无法读取数据。你抛出一个错误。
  • 5) 如果强制转换成功,则返回包含已存储密码的字符串。

现在,您已经实现了存储和检索钥匙串物品所需的所有功能。接下来,您将连接您的用户界面,以便可以看到它的工作!


Using Keychain Services in SwiftUI

打开Models组下的NoteData.swift。从应用程序访问密码使用两种方法:getStoredPassword()读取密码,而updateStoredPassword(_ :)设置密码。还有一个属性isPasswordBlank

首先,在类的开头删除语句let passwordKey =“ Password”

现在,用以下代码替换现有的getStoredPassword()方法:

func getStoredPassword() -> String {
  let kcw = KeychainWrapper()
  if let password = try? kcw.getGenericPasswordFor(
    account: "RWQuickNote",
    service: "unlockPassword") {
    return password
  }

  return ""
}

此方法创建一个KeychainWrapper并调用getGenericPasswordFor(account:service :)读取并返回密码。 try?表达式将异常转换为nil。 如果搜索失败,这将导致该方法返回一个空字符串。

接下来,将updateStoredPassword(_ :)替换为:

func updateStoredPassword(_ password: String) {
  let kcw = KeychainWrapper()
  do {
    try kcw.storeGenericPasswordFor(
      account: "RWQuickNote",
      service: "unlockPassword",
      password: password)
  } catch let error as KeychainWrapperError {
    print("Exception setting password: \(error.message ?? "no message")")
  } catch {
    print("An error occurred setting the password.")
  }
}

您可以使用KeychainWrapper使用相同的accountservice来设置密码。 对于此应用,您将所有错误打印到控制台。

现在构建并运行。 该应用再次运行时要求您设置密码。 但是您的应用程序不再从UserDefaults读取数据,这是不安全的。 而是使用加密的钥匙串来存储和检索密码。

输入新密码,您会看到之前的运行记录。 点按两次锁定按钮。 这将使用您刚设置的密码锁定然后解锁笔记。 您的密码有效,并且出现note

您现在可以将密码添加到钥匙串中,并且可以检索该密码以验证用户身份。 但是您仍然需要添加一些其他方法来完成应用程序中Keychain Services的实现。


Updating a Password in the Keychain

在应用程序运行且note解锁的情况下,点击双箭头按钮以更改密码。 输入新密码,然后点击Set Password。 控制台窗口中出现错误:

如果该item已经存在,则不能向钥匙串添加任何东西! 相反,您需要更新update存储的项目。

打开KeychainServices.swift。 在KeychainWrapper类的末尾添加以下代码:

func updateGenericPasswordFor(
  account: String,
  service: String,
  password: String
) throws {
  guard let passwordData = password.data(using: .utf8) else {
    print("Error converting value to data.")
    return
  }
  // 1
  let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecAttrService as String: service
  ]

  // 2
  let attributes: [String: Any] = [
    kSecValueData as String: passwordData
  ]

  // 3
  let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
  guard status != errSecItemNotFound else {
    throw KeychainWrapperError(
      message: "Matching Item Not Found", 
      type: .itemNotFound)
  }
  guard status == errSecSuccess else {
    throw KeychainWrapperError(status: status, type: .servicesError)
  }
}

此代码类似于您之前添加的storeGenericPasswordFor(account:service:password :)。但是,更新需要两个词典,一个包含搜索查询,另一个具有所需的更改。细目如下:

  • 1) 搜索query指定您要更新的数据。您提供的属性与之前创建的搜索查询一样,但是您没有使用匹配限制和返回属性之类的搜索参数。该功能将更新所有匹配的条目。
  • 2) 第二个词典包含要更新的数据。您可以指定对该类有效的任何或所有属性,但仅包括要更改的属性。在这里,您仅指定新密码。但是,如果要存储这些属性的新值,则也可以设置service or account
  • 3) SecItemUpdate(_:_ :)使用上面两个词典的内容并执行更新。您会看到的最常见错误是The specified attribute does not exist。此错误表明钥匙串服务未找到与搜索查询匹配的内容。

您不需要继续检查和决定是否需要编写新的钥匙串项或更新现有的钥匙串项,或者如果您调用错误的方法来处理出现的错误。您现在要解决此问题。

storeGenericPasswordFor(account:service:password :)中,找到switch status语句并在默认值上方添加新的case

case errSecDuplicateItem:
  try updateGenericPasswordFor(
    account: account, 
    service: service, 
    password: password)

errSecDuplicateItem是存储现有项目时返回的状态。 现在,如果您尝试存储现有item,则可以选择更新它。

构建并运行。 使用您之前设置的密码解锁note。 (请记住,上面的密码更改尚未完成。尝试添加已经存在的项目导致异常。)再次尝试更改密码-成功!

现在,您的应用程序可以添加,检索和更新密码。 但是仍然缺少一项动作。 您需要能够从钥匙串中删除一个值。


Deleting a Password From the Keychain

仍在KeychainServices.swift中,将以下代码添加到KeychainWrapper的末尾:

func deleteGenericPasswordFor(account: String, service: String) throws {
  // 1
  let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecAttrService as String: service
  ]

  // 2
  let status = SecItemDelete(query as CFDictionary)
  guard status == errSecSuccess || status == errSecItemNotFound else {
    throw KeychainWrapperError(status: status, type: .servicesError)
  }
}

同样,此代码大部分与您之前添加的用于添加和更新item的代码相似。 注意更改:

  • 1) 尽管查询看起来很像添加和更新,但这里您没有提供新值。

注意:确保查询query仅标识要删除的一个或多个项目。 如果多个项目与查询query匹配,则钥匙串服务会删除所有这些项目。

  • 2) 您使用query调用SecItemDelete(_ :)来删除项目。 没有撤消!

将以下代码添加到storeGenericPasswordFor(account:service:password :)的顶部以调用新方法:

if password.isEmpty {
  try deleteGenericPasswordFor(
    account: account,
    service: service)
  return
}

现在,您可以添加,检索,更新和删除钥匙串中的项目。 这四种方法为您提供了处理钥匙串中项目所需的核心功能。

构建并运行该应用程序,然后点击“重置”按钮。 该应用程序将密码和note设置为空字符串。 由于您刚进行了更改,因此重置按钮现在可以删除密码和note

重新运行该应用并设置初始密码。 点按两次锁定按钮,以确认您的新密码已将note解锁。 现在点击箭头按钮以更改密码。 再次点击锁定按钮两次,以验证该应用是否存储了您的新密码。

恭喜你!现在,您已将钥匙串服务(Keychain Services)添加到您的应用中以保护密码。

每次键入密码来查看note对于用户而言都是乏味的。接下来,您将添加生物特征认证(biometric authentication),以实现更简单但仍安全的解锁。


Biometric Authentication in SwiftUI

生物特征认证(Biometric authentication)使用您身体的独特特征来验证您的身份。苹果提供了两种生物特征认证方法:

  • Touch ID使用您的指纹。
  • Face ID使用您脸部的独特形状。

两种方法都比密码更快,更容易。而且它们更加安全。有人可能会猜出您的密码,但是复制您的指纹或面部属于间谍电影的世界,而不是现实世界!

在下一节中,您将在应用程序中内置生物特征认证。

1. Enabling Biometric Authentication in Your App

打开ToolbarView.swift。该视图定义了编辑器上方显示的工具栏。它包括锁定按钮,用于锁定和解锁笔记。这是向您的应用程序添加生物特征认证的理想场所。您将使用Local Authentication框架来执行此操作。

在文件的顶部,在现有import语句之后添加以下代码:

import LocalAuthentication

LocalAuthentication框架允许您的应用访问用户解锁设备所用的相同系统,即密码,Touch IDFace ID。 当前,当note被锁定并且用户点击锁定按钮时,该应用会将showUnlockModal状态属性设置为true。 将showUnlockModal设置为true会显示请求和验证密码的视图。

您将其更改为执行生物特征认证。 如果失败或不可用,您将退回到密码视图。

ToolbarView.swift中,在body上方添加以下方法:

func tryBiometricAuthentication() {
  // 1
  let context = LAContext()
  var error: NSError?

  // 2
  if context.canEvaluatePolicy(
    .deviceOwnerAuthenticationWithBiometrics,
    error: &error) {
    // 3
    let reason = "Authenticate to unlock your note."
    context.evaluatePolicy(
      .deviceOwnerAuthenticationWithBiometrics,
      localizedReason: reason) { authenticated, error in
      // 4
      DispatchQueue.main.async {
        if authenticated {
          // 5
          self.noteLocked = false
        } else {
          // 6
          if let errorString = error?.localizedDescription {
            print("Error in biometric policy evaluation: \(errorString)")
          }
          self.showUnlockModal = true
        }
      }
    }
  } else {
    // 7
    if let errorString = error?.localizedDescription {
      print("Error in biometric policy evaluation: \(errorString)")
    }
    showUnlockModal = true
  }
}

每个步骤的运作方式如下:

  • 1) 您可以使用LAContext对象访问生物特征认证。这将从用户交互中收集生物特征,并与设备上的Secure Enclave通信。 LocalAuthentication可以早于Swift并使用诸如NSError之类的Objective-C模式。
  • 2) 您首先检查验证是否可用。第一个参数.deviceOwnerAuthenticationWithBiometrics,请求生物特征认证。
  • 3) 您描述了为什么要在reason中合理使用身份验证的原因,iOS会向用户显示该身份验证。调用evalidatePolicy(_:localizedReason:reply :)要求身份验证。设备将执行Face IDTouch ID身份验证,以当前设备上可用的方式为准。调用在返回时执行该块。
  • 4) 由于您是从块执行此代码并更改UI,因此必须确保更改在UI线程上运行。
  • 5) 如果身份验证成功,则将note设置为未锁定。请注意,您没有获得有关身份验证的更多信息!您仅发现它成功或失败。
  • 6) 如果身份验证失败,则打印出现在块中的任何错误。您还可以将showUnlockModal状态设置为true。此操作将迫使您的应用退回到手动密码行为。
  • 7) 如果初始检查失败,则表示生物特征认证不可用。您打印收到的错误,然后显示解锁视图。同样,您提供一个后备身份验证路径。

某些设备没有身份验证,或者用户可能未设置身份验证。认证有时会失败。无论失败原因是什么,您都必须始终为用户提供适当的方式来进行生物识别认证!

您需要进行另一项更改才能启用此功能。按Command-F来找到// Biometric Authentication Point。将下一行self.showUnlockModal = true替换为对新方法的调用:

self.tryBiometricAuthentication()

现在您可以测试生物特征认证功能了!

2. Simulating Biometric Authentication in Xcode

幸运的是,该模拟器能够在测试过程中为您模拟生物特征认证。 您选择的模拟设备决定了生物特征认证的类型。

3. Simulating Touch ID Authentication

选择iPhone SE (2nd generation)以测试Touch ID。 构建并运行。 点击解锁按钮,将出现普通的解锁窗口。 如果您在控制台中查看,则会看到错误消息:No identities are enrolled

模拟器不会自动将设备注册为生物特征认证。 您必须进行设置。 在模拟器中,选择Features ▸ Touch ID ▸ Enrolled。 输入密码以解锁记事。 然后点击锁定按钮两次。 这将锁定它并尝试再次将其解锁。 这次,您会看到Touch ID提示。

要模拟生物特征认证,请使用Features ▸ Touch ID菜单下的其他选项。 要执行成功的身份验证,请选择Features ▸ Touch ID ▸ Matching Touch。 片刻之后,身份验证请求将消失,并且笔记将解锁。

您还可以验证身份验证失败的行为。 点击锁定按钮以锁定笔记。 再次点击以尝试身份验证。 要模拟失败的身份验证,请选择Features ▸ Touch ID ▸ Non-matching Touch。 您会看到出现失败的提示:

在提示上选择任一选项将使身份验证失败,并显示上一个解锁页面。 输入密码以确认您仍然可以解锁该笔记。

您还可以让用户输入设备PIN作为后备。 为此,请再次打开ToolbarView.swift。 使用Command-F查找context.evaluatePolicy。 将.deviceOwnerAuthenticationWithBiometrics更改为.deviceOwnerAuthentication

现在构建并运行。 身份验证失败,请注意,Enter Password选项已更改为Enter Passcode。 点按它,您会看到模拟器passcode entry。 对于模拟器,任何passcode值都可以使用。 输入密码,note将解锁。

4. Simulating Face ID Authentication

Face IDTouch ID一样。 但是Face ID有一个新要求。 您必须将NSFaceIDUsageDescription密钥添加到应用程序的Info.plist中,否则授权请求将失败。

打开Info.plist。 右键单击页面的空白部分,然后选择Add Row。 滚动浏览键列表,然后选择Privacy – Face ID Usage Description。 双击值列下的,然后输入To allow you to unlock your note without entering your password

现在,将设备更改为支持Face IDiPhone 11 Pro。 构建并运行。 在模拟器中,选择Features ▸ Face ID ▸ Enrolled。 菜单名称已更改,以反映新的生物特征认证方法。

在模拟器上的应用中,选择一个新密码。 进入便笺视图后,点击两次锁定按钮。 iOS提示您允许生物识别身份验证:

点击OK。 现在,您会看到Face ID提示。 选择Features ▸ Face ID ▸ Matching Face,您会看到note解锁。

5. Making the Authentication Method Visible to the User

现在,您将再添加一次触摸touch,以使用户知道该应用程序支持生物识别身份验证。

ToolbarView.swift中,在import语句之后直接添加以下代码:

func getBiometricType() -> String {
  let context = LAContext()

  _ = context.canEvaluatePolicy(
    .deviceOwnerAuthenticationWithBiometrics,
    error: nil)
  switch context.biometryType {
  case .faceID:
    return "faceid"
  case .touchID:
    // In iOS 14 and later, you can use "touchid" here
    return "lock"
  case .none:
    return "lock"
  @unknown default:
    return "lock"
  }
}

此代码在context上使用biometryType属性来确定可用的生物特征认证的类型。 它返回要匹配的SF符号的名称,或者返回未知的当前锁定符号。 在iOS 14之前,没有Touch ID符号。

找到// Lock Icon注释,然后将Image代码更改为:

Image(systemName: noteLocked ? getBiometricType() : "lock.open")

构建并运行。 现在,锁定按钮指示它使用Face ID进行身份验证!

您可能已经保护了密码,但是note本身仍以纯文本形式存储在User Default中。 查看我们的CryptoKit tutorial教程,以获取有关保护大量数据的信息。

对于其他类别的钥匙串items,Apple的Keychain Services文档应该是您的第一站。 其他类的工作原理几乎相同,并且文档中包含其中大多数的Swift示例。

对于从UIKit角度来看的类似材料,请阅读 How To Secure iOS User Data: The Keychain and Biometrics — Face ID or Touch ID

以及关于钥匙串的另一个角度,请参阅 Keychain Services API Tutorial for Passwords in Swift

后记

本篇主要讲述了SwiftUI的钥匙串服务和生物识别,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345