关注我们

iOS安全基础之钥匙串与哈希

LzersLzers 安全快讯 2018-04-08 589153 0

iOS安全基础之钥匙串与哈希

不过此时,当你点击登录按钮时没有任何反应,这是因为用户的凭证还没有办法进行保存。因此,你要做的第一件事就是要先添加用户的凭证。

为什么安全是苹果的重中之重?

在深入了解代码之前,你应该明白为什么你的应用程序需要强有力的安全保证。如果你要存储比较隐私的用户数据,如电子邮件,密码或银行帐户信息,则应用程序的安全性尤其重要。

iOS安全基础之钥匙串与哈希

不过,要直接与钥匙串进行交互,那是相当复杂的,尤其是在Swift中,因为你必须使用主要由C语言编写的安全框架。

幸运的是,你可以通过从下载材料中的样本代码GenericKeychain借用Swift封装器来避免使用这些低级API。就在下载材料中,KeychainPasswordItem已为钥匙串提供了一个易于使用的Swift接口。

使用钥匙串

打开AuthViewController.swift,该视图控制器会负责你最初看到的登录表单。如果向下滚动到Actions部分,你会注意到signInButtonPressed没有做任何事情。所以你需要花点时间,来做一些小的修改,你可以将以下内容添加到Helpers的底部:

private func signIn() {
  // 1
  view.endEditing(true)
   
  // 2
  guard let email = emailField.text, email.count > 0 else {
    return
  }
  guard let password = passwordField.text, password.count > 0 else {
    return
  }
   
  // 3
  let name = UIDevice.current.name
  let user = User(name: name, email: email)
}

接下来会发生以下改变:

1.你可以通过关闭键盘操作来避免用户的操作行为被人追踪;

2.你可以接收用户输入的电子邮件和密码,如果Eithe类是零长度,那么你就不要继续往下。在真实的应用程序中,此时用户就会收到错误提示。

3.你可以为用户分配一个名称,就本文而言,你可以从设备名称中分配一个名称。

注意:你可以进入“系统偏好设置▸共享”并在顶部更改计算机名称来更改你的Mac的名称(由sim使用)。此外,你可以进入 “设置▸常规▸关于▸名称”来更改iPhone的名称。

现在在signInButtonPressed中添加以下内容:

signIn()

当signInButtonPressed被触发时,会调用你的signIn方法,
找到textFieldShouldReturn并将 case TextFieldTag.password.rawValue中的break替换为以下内容。

signIn()

现在signIn()被调用,当用户在键盘上点击返回时,密码字段就会出现焦点并包含文本。不过此时,signIn()尚未完成。你仍然需要存储用户对象以及密码,这些都会在helper类中实现。

打开AuthController.swift,这是一个静态类,它将保存与此应用程序的身份验证相关的逻辑。

首先,在isSignedIn以上的文件顶部添加以下内容:

static let serviceName = "FriendvatarsService"

现在signIn()被调用,当用户在键盘上点击返回时,密码字段就会出现焦点并包含文本。不过此时,signIn()尚未完成。你仍然需要存储用户对象以及密码,这些都会在helper类中实现。

打开AuthController.swift,这是一个静态类,它将保存与此应用程序的身份验证相关的逻辑。

首先,在isSignedIn以上的文件顶部添加以下内容:

class func signIn(_ user: User, password: String) throws {
  try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(password)
   
  Settings.currentUser = user
}

此方法会将用户的登录信息安全地存储在钥匙串中,然后创建了一个KeychainPasswordItem,其中包含你定义的服务名称和唯一标识符(帐户)。

对于这个应用程序样本,用户的电子邮件会被用作钥匙串的标识符,但对其他样本来说也可以是唯一的用户标识或用户名。最后,Settings.currentUser由存储在UserDefaults中的 user设置的。

不过,此方法并不是最完美的,因为直接存储用户密码并不是最安全的做法。例如,如果攻击者破坏了苹果的钥匙串,他就可以用纯文本形式读取用户的密码。所以更好的解决方案是存储由用户身份构建的哈希。

在AuthController.swift的顶部,由Foundation导入以下添加内容:

import CryptoSwift

CryptoSwift是用Swift编写的许多标准加密算法中最受欢迎的集合之一,不过加密过程是个技术活,需要正确地使用才可以。。使用一个流行的安全库意味着你不必从头在设计一遍那些标准化的哈希函数,最好的加密技术是向公众开放的。

注意:苹果的CommonCrypto框架为你提供了许多有用的哈希函数,但在Swift中与它进行交互并不容易。这就是为什么我们选CryptoSwift库的原因。

接下来添加以下的signIn:

class func passwordHash(from email: String, password: String) -> String {
  let salt = "x4vV8bGgqqmQwgCoyXFQj+(o.nUNQhVP7ND"
  return "\(password).\(email).\(salt)".sha256()
}

实现这种方法的前提是需要一个电子邮件和密码,并返回一个哈希字符串。通过加入盐值(salt)即盐化可以用来制作通用密码的唯一字符串。 sha256()是一种CryptoSwift方法,可以在输入字符串上完成SHA-2哈希。

在前面我讲过,攻击者可以通过泄露了钥匙串发现这个哈希。攻击者可能会创建一个常用密码表及其哈希表来与此哈希进行比较。如果你没有进行盐化处理,那么输入的哈希密码照样会被攻击。盐化会增加攻击的复杂性,此外,你可以将用户的电子邮件和密码与盐化值结合在一起以创建一个不易被破解的哈希。

注意:对于使用服务器后端进行身份验证,应用程序和服务器将共享相同的盐化值,这就允许他们以相同的方式构建哈希并比较两个哈希来验证身份。

返回signIn(_:password:),将调用savePassword的行替换为:

let finalHash = passwordHash(from: user.email, password: password)
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(finalHash)

这样signIn现在就存储了一个强大的哈希,而不是一个原始密码。现在是时候将其添加到视图控制器了,
返回AuthViewController.swift并将以下内容添加到signIn()的底部。

do {
  try AuthController.signIn(user, password: password)
} catch {
  print("Error signing in: \(error.localizedDescription)")
}

虽然这会存储用户并保存哈希密码,但当身份认证更改时,AppController.swift需要提前得到通知,所以此时用户要登录应用程序就比较慢了。

你可能已经注意到AuthController.swift有一个名为isSignedIn的静态变量。目前,即使用户登录,它总是返回false。

在AuthController.swift中,将isSignedIn更新为以下内容:

static var isSignedIn: Bool {
  // 1
  guard let currentUser = Settings.currentUser else {
    return false
  }
   
  do {
    // 2
    let password = try KeychainPasswordItem(service: serviceName, account: currentUser.email).readPassword()
    return password.count > 0
  } catch {
    return false
  }
}

接下来会发生以下改变:

1.你可以马上检查存储在UserDefaults中的当前用户,如果没有存储的用户,就不会有一个标识符来查找来自钥匙串中的密码哈希,这就代表用户没有登录。

2.你可以从钥匙串中读取密码哈希,如果密码存在且不为空,则就表示该用户已登录。

现在,AppController.swift中的handleAuthState将正常工作,但登录应用程序后才能正确更新UI。否则,只能通知应用程序更改状态(如身份验证)。

将以下内容添加到AuthController.swift的底部:

extension Notification.Name {
   
  static let loginStatusChanged = Notification.Name("com.razeware.auth.changed")
   
}

在编写自定义通知时使用反向域标识符是一种很好的做法,这通常来自于应用程序的bundle标识符。使用唯一标识符可以在调试时提供帮助,这样任何与你的通知相关的内容都可以从日志中提到的其他框架中被提取出来。

若要使用自定义的通知名称,请将以下内容添加到signIn(_:password:)的底部:

NotificationCenter.default.post(name: .loginStatusChanged, object: nil)

这样该通知就会被应用程序的其他部分被看见,在AppController.swift的内部,你可以在show(in:)之上添加一个init方法。

init() {
  NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleAuthState),
    name: .loginStatusChanged,
    object: nil
  )

这样你一旦登录,经过注册的AppController就会通知你已经登录的消息,它会在触发时调用handleAuthState。这样在使用任何电子邮件和密码组合登录后,你都会看到一各好友列表。

iOS安全基础之钥匙串与哈希

你会发现,虽然登录过程很顺利,但却没有办法退出应用程序。这实际上很容易实现,因为会有一个对身份验证状态更改的通知。

返回AuthController.swift并在 signIn(_:password:)下面添加以下内容。

class func signOut() throws {
  // 1
  guard let currentUser = Settings.currentUser else {
    return
  }
   
  // 2
  try KeychainPasswordItem(service: serviceName, account: currentUser.email).deleteItem()
   
  // 3
  Settings.currentUser = nil
  NotificationCenter.default.post(name: .loginStatusChanged, object: nil)
}

其主要作用是:

1.检查你是否已经存储了一个当前用户,如果没有,就可以提前退出了;

2.从钥匙串中删除密码哈希;

3.清除用户对象并发布通知;

要连接它,就请跳转到FriendsViewController.swift,并将以下内容添加到当前空的signOut中:

try? AuthController.signOut()

当选择注销按钮时,程序就会调用你设置的新方法来清除登录用户的数据。

在应用程序中处理错误是一个好主意, 构建并运行,然后点击注销按钮。

iOS安全基础之钥匙串与哈希

现在你就有了一个在应用程序中使用身份验证的完整示例!

哈希

还记得刚刚说到的朋友列表里只有名字,没有头像的问题吗?现在我就来解决这个问题。

在FriendsViewController.swift中,会显示用户模型对象的列表。要想在朋友列表视图中显示头像,就必须先搞清楚一件事,那就是用户只有两个属性,名称和电子邮件,那你应该如何添加图像呢?

事实证明,有一项服务可以在接受电子邮件地址的同时将该邮件人的头像显示出来,这个服务就是Gravatar。

Gravatar是Globally Recognized Avatar的缩写,是gravatar推出的一项服务,意为“全球通用头像”。如果在Gravatar的服务器上放置了你自己的头像,那么在任何支持Gravatar的blog或者留言本上留言时,只要提供你与这个头像关联的email地址,就能够显示出你的Gravatar头像来。

我们在很多博客或者网站留言,评论的时候会看到有的人头像很酷很个性化,但是这个博客和网站本身并没有提供设置头像的功能,感觉有点神奇,那么是怎么做到的呢?其实这是使用了Gravatar。

Gravatar的概念首先是在国外的独立WordPress博客中兴起的,当你到任何一个支持Gravatar的网站留言时,这个网站都会根据你所提供的Email地址为你显示出匹配的头像。当然,这个头像,是需要你事先到Gravatar的网站注册并上传的,否则,在这个网站上,就只会显示成一个默认的头像。

所以你唯一需要做的就是向Gravatar提出请求并获取他们匹配的头像。为此,你就要创建其电子邮件的MD5哈希以构建请求URL。如果你查看Gravatar网站上的文档,你会发现它需要一个哈希邮件地址来构建用户的请求。由于你可以利用CryptoSwift,这将是小菜一碟。只需在tableView(_:cellForRowAt:)中添加以下代替关于Gravatar的注释即可。

// 1
let emailHash = user.email.trimmingCharacters(in: .whitespacesAndNewlines)
                          .lowercased()
                          .md5()
// 2
if let url = URL(string: "https://www.gravatar.com/avatar/" + emailHash) {
  URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data, let image = UIImage(data: data) else {
      return
    }
     
    // 3
    self.imageCache.setObject(image, forKey: user.email as NSString)
     
    DispatchQueue.main.async {
      // 4
      self.tableView.reloadRows(at: [indexPath], with: .automatic)
    }
  }.resume()
}

具体进程如下:

1.首先根据Gravatar的文档将电子邮件规范化,然后创建MD5哈希;

2.通过你构建的Gravatar URL和URLSession,从返回的数据中加载UIImage;

3.缓存与头像有关的图像以避免重复获取电子邮件地址;

4.重新加载表格视图中的行,以便显示与头像有关的图像;

构建并运行,现在,你可以查看朋友的头像和名称了。

iOS安全基础之钥匙串与哈希


版权声明

本文仅代表作者观点,不代表黑白网立场。
如文章侵犯了您的权利,请通过邮箱联系我们删除。
详情查看:版权纠纷
E-Mail:server@heibai.org

喜欢0评论已闭