[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?

前言

时下,是一个信息爆炸的时代,信息的传播途径也是层出不穷,市面上出现了各种各样的设备来收集、传播、存储我们的数据,用户的隐私受到了极大的挑战。这些设备中智能手机尤为更胜,做为 iOS 开发者保护用于的数据也是我们应尽的责任。今天我们讨论的是用户登录数据如何免于泄露。
对于用户登录数据的保护,大家首先想到的应该就是 Keychain, 为了更进一步确保数据安全苹果公司还为我们提供了另外一层安全保护措施——Face IDTouch ID.
iPhone 5s 以及更新的 iPhone 搭载了A7及更新的芯片,这些芯片允许您将用户的生物特征数据存储到一个安全的区域。也就是说,我们可以通过Face IDTouch ID 将用户生物特征数据存储到 Keychain里,从而提高用户数据的安全性。本文中我们将探寻如何使用Face IDTouch ID来实现登录认证。

正文

创建一个工程,实现一个简单的登录页面(你也可以从这里下载 download), 运行程序如图所示:

[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?_第1张图片
image.png

打开这个工程,你需要把相应的配置改成你自己的,如:
[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?_第2张图片
image.png

打开 LoginViewController.swift , 在 managedObjectContext:下添加几个常量:

let usernameKey = "Batman"
let passwordKey = "Hello Bruce!"

这是我们内置的用户名和密码,作为校验用户的凭证。让后我们添加一个登录校验函数:

func checkLogin(username: String, password: String) -> Bool {
  return username == usernameKey && password == passwordKey
}

接下来实现一个登录函数, 函数内部实现:

if checkLogin(username: usernameTextField.text!, password: passwordTextField.text!) {
  performSegue(withIdentifier: "dismissLogin", sender: self)
}

这样一个简单的登录实现就完成了。这种简单的用户校验过程看起来是没有什么问题,实际上我们的用户信息(账号和密码)不应该存储在应用程式中,这样很容易被破解。因此我们可以将其存储到Keychain中来提高其安全性。如果你想更多的了解Keychain,请移步 Basic Security in iOS 5 – Part 1.

Keychain

Keychain 如何存储数据呢?苹果官方提供了实例代码GenericKeychain

在我们的工程中创建 KeychainPasswordItem.swift如:

[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?_第3张图片
image.png

思路:将用户名和密码存入如 Keychain 中,然后在根据用户的输入进行校验。
在使用 Keychain之前,你需要对应用进行配置保证数据可以写入 Keychain中,然后打开 LoginViewController.swift

// Keychain Configuration
struct KeychainConfiguration {
  static let serviceName = "TouchMeIn"
  static let accessGroup: String? = nil
}

然后在 managedObjectContext:加入:

var passwordItems: [KeychainPasswordItem] = []
let createLoginButtonTag = 0
let loginButtonTag = 1

@IBOutlet weak var loginButton: UIButton!

我们在声明一个提醒登陆失败的函数:

private func showLoginFailedAlert() {
  let alertView = UIAlertController(title: "Login Problem",
                                    message: "Wrong username or password.",
                                    preferredStyle:. alert)
  let okAction = UIAlertAction(title: "Foiled Again!", style: .default)
  alertView.addAction(okAction)
  present(alertView, animated: true)
}

接下来我们登录函数里的实现就可以改为:

@IBAction func loginAction(sender: UIButton) {
  // 1
  // Check that text has been entered into both the username and password fields.
  guard let newAccountName = usernameTextField.text,
    let newPassword = passwordTextField.text,
    !newAccountName.isEmpty,
    !newPassword.isEmpty else {
      showLoginFailedAlert()
      return
  }
    
  // 2
  usernameTextField.resignFirstResponder()
  passwordTextField.resignFirstResponder()
    
  // 3
  if sender.tag == createLoginButtonTag {
    // 4
    let hasLoginKey = UserDefaults.standard.bool(forKey: "hasLoginKey")
    if !hasLoginKey && usernameTextField.hasText {
      UserDefaults.standard.setValue(usernameTextField.text, forKey: "username")
    }
      
    // 5
    do {
      // This is a new account, create a new keychain item with the account name.
      let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                              account: newAccountName,
                                              accessGroup: KeychainConfiguration.accessGroup)
        
      // Save the password for the new item.
      try passwordItem.savePassword(newPassword)
    } catch {
      fatalError("Error updating keychain - \(error)")
    }
      
    // 6
    UserDefaults.standard.set(true, forKey: "hasLoginKey")
    loginButton.tag = loginButtonTag
    performSegue(withIdentifier: "dismissLogin", sender: self)
  } else if sender.tag == loginButtonTag {
     // 7
    if checkLogin(username: newAccountName, password: newPassword) {
      performSegue(withIdentifier: "dismissLogin", sender: self)
    } else {
      // 8
      showLoginFailedAlert()
    }
  }
}

我们的checkLogin(username:password:)函数的实现也改为:

func checkLogin(username: String, password: String) -> Bool {
  guard username == UserDefaults.standard.value(forKey: "username") as? String else {
    return false
  }
    
  do {
    let passwordItem = KeychainPasswordItem(service: KeychainConfiguration.serviceName,
                                            account: username,
                                            accessGroup: KeychainConfiguration.accessGroup)
    let keychainPassword = try passwordItem.readPassword()
    return password == keychainPassword
  } catch {
    fatalError("Error reading password from keychain - \(error)")
  }
}

删除原来定义的常量代码,实现viewDidLoad()

// 1
let hasLogin = UserDefaults.standard.bool(forKey: "hasLoginKey")
    
// 2
if hasLogin {
  loginButton.setTitle("Login", for: .normal)
  loginButton.tag = loginButtonTag
  createInfoLabel.isHidden = true
} else {
  loginButton.setTitle("Create", for: .normal)
  loginButton.tag = createLoginButtonTag
  createInfoLabel.isHidden = false
}
    
// 3
if let storedUsername = UserDefaults.standard.value(forKey: "username") as? String {
  usernameTextField.text = storedUsername
}

这样我们的Keychain存储数据就完成了。
那么我们的 Touch ID呢?
搭建Touch ID界面(详细过程略):

[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?_第4张图片
image.png

添加本地验证

Local Authentication库为我们提供了实现生物特征识别的 Api ,我们在项目中导入 Local Authentication。官方文档是这样描述的:
“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”
安全性提升到直接取决于用户的——手指和脸。在 iOS 11中开始支持人脸识别, 你需要在工程的 info plist文件中声明 Privacy – Face ID Usage Description:

[译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?_第5张图片
image.png

创建 TouchIDAuthentication.swift文件:

import LocalAuthentication

enum BiometricType {
  case none
  case touchID
  case faceID
}

class BiometricIDAuth {
  let context = LAContext()
  var loginReason = "Logging in with Touch ID"

  func biometricType() -> BiometricType {
    let _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
    switch context.biometryType {
    case .none:
      return .none
    case .touchID:
      return .touchID
    case .faceID:
      return .faceID
    }
  }

  func canEvaluatePolicy() -> Bool {
    return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
  }

  func authenticateUser(completion: @escaping (String?) -> Void) {
    guard canEvaluatePolicy() else {
      completion("Touch ID not available")
      return
    }
    
    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: loginReason) { (success, evaluateError) in
      if success {
        DispatchQueue.main.async {
          // User authenticated successfully, take appropriate action
          completion(nil)
        }
      } else {
        let message: String

        switch evaluateError {
        case LAError.authenticationFailed?:
          message = "There was a problem verifying your identity."
        case LAError.userCancel?:
          message = "You pressed cancel."
        case LAError.userFallback?:
          message = "You pressed password."
        case LAError.biometryNotAvailable?:
          message = "Face ID/Touch ID is not available."
        case LAError.biometryNotEnrolled?:
          message = "Face ID/Touch ID is not set up."
        case LAError.biometryLockout?:
          message = "Face ID/Touch ID is locked."
        default:
          message = "Face ID/Touch ID may not be configured"
        }
        completion(message)                            }
    }
  }
}

你可以在这里下载完整项目project
iOS 安全指南

文献

How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID

你可能感兴趣的:([译]如何防止 iOS 用户数据泄露?Keychain、Biometrics、Face ID 还是 Touch ID ?)