[譯]如何防止 iOS 用戶數(shù)據(jù)泄露?Keychain、Biometrics、Face ID 還是 Touch ID ?

前言

時(shí)下,是一個(gè)信息爆炸的時(shí)代,信息的傳播途徑也是層出不窮,市面上出現(xiàn)了各種各樣的設(shè)備來收集、傳播、存儲(chǔ)我們的數(shù)據(jù),用戶的隱私受到了極大的挑戰(zhàn)。這些設(shè)備中智能手機(jī)尤為更勝,做為 iOS 開發(fā)者保護(hù)用于的數(shù)據(jù)也是我們應(yīng)盡的責(zé)任。今天我們討論的是用戶登錄數(shù)據(jù)如何免于泄露。
對(duì)于用戶登錄數(shù)據(jù)的保護(hù),大家首先想到的應(yīng)該就是 Keychain, 為了更進(jìn)一步確保數(shù)據(jù)安全蘋果公司還為我們提供了另外一層安全保護(hù)措施——Face IDTouch ID.
iPhone 5s 以及更新的 iPhone 搭載了A7及更新的芯片,這些芯片允許您將用戶的生物特征數(shù)據(jù)存儲(chǔ)到一個(gè)安全的區(qū)域。也就是說,我們可以通過Face IDTouch ID 將用戶生物特征數(shù)據(jù)存儲(chǔ)到 Keychain里,從而提高用戶數(shù)據(jù)的安全性。本文中我們將探尋如何使用Face IDTouch ID來實(shí)現(xiàn)登錄認(rèn)證。

正文

創(chuàng)建一個(gè)工程,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的登錄頁(yè)面(你也可以從這里下載 download), 運(yùn)行程序如圖所示:

image.png

打開這個(gè)工程,你需要把相應(yīng)的配置改成你自己的,如:
image.png

打開 LoginViewController.swift , 在 managedObjectContext:下添加幾個(gè)常量:

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

這是我們內(nèi)置的用戶名和密碼,作為校驗(yàn)用戶的憑證。讓后我們添加一個(gè)登錄校驗(yàn)函數(shù):

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

接下來實(shí)現(xiàn)一個(gè)登錄函數(shù), 函數(shù)內(nèi)部實(shí)現(xiàn):

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

這樣一個(gè)簡(jiǎn)單的登錄實(shí)現(xiàn)就完成了。這種簡(jiǎn)單的用戶校驗(yàn)過程看起來是沒有什么問題,實(shí)際上我們的用戶信息(賬號(hào)和密碼)不應(yīng)該存儲(chǔ)在應(yīng)用程式中,這樣很容易被破解。因此我們可以將其存儲(chǔ)到Keychain中來提高其安全性。如果你想更多的了解Keychain,請(qǐng)移步 Basic Security in iOS 5 – Part 1.

Keychain

Keychain 如何存儲(chǔ)數(shù)據(jù)呢?蘋果官方提供了實(shí)例代碼GenericKeychain

在我們的工程中創(chuàng)建 KeychainPasswordItem.swift如:

image.png

思路:將用戶名和密碼存入如Keychain 中,然后在根據(jù)用戶的輸入進(jìn)行校驗(yàn)。
在使用 Keychain之前,你需要對(duì)應(yīng)用進(jìn)行配置保證數(shù)據(jù)可以寫入 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!

我們?cè)诼暶饕粋€(gè)提醒登陸失敗的函數(shù):

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)
}

接下來我們登錄函數(shù)里的實(shí)現(xiàn)就可以改為:

@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:)函數(shù)的實(shí)現(xiàn)也改為:

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)")
  }
}

刪除原來定義的常量代碼,實(shí)現(xiàn)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存儲(chǔ)數(shù)據(jù)就完成了。
那么我們的 Touch ID呢?
搭建Touch ID界面(詳細(xì)過程略):

image.png

添加本地驗(yàn)證

Local Authentication庫(kù)為我們提供了實(shí)現(xiàn)生物特征識(shí)別的 Api ,我們?cè)陧?xiàng)目中導(dǎo)入 Local Authentication。官方文檔是這樣描述的:
“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”
安全性提升到直接取決于用戶的——手指和臉。在 iOS 11中開始支持人臉識(shí)別, 你需要在工程的 info plist文件中聲明 Privacy – Face ID Usage Description:

image.png

創(chuàng)建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)                            }
    }
  }
}

你可以在這里下載完整項(xiàng)目project
iOS 安全指南

文獻(xiàn)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容