版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.10 星期三 |
前言
在這個信息爆炸的年代,特別是一些敏感的行業,比如金融業和銀行卡相關等等,這都對
app
的安全機制有更高的需求,很多大公司都有安全 部門,用于檢測自己產品的安全性,但是及時是這樣,安全問題仍然被不斷曝出,接下來幾篇我們主要說一下app
的安全機制。感興趣的看我上面幾篇。
1. APP安全機制(一)—— 幾種和安全性有關的情況
2. APP安全機制(二)—— 使用Reveal查看任意APP的UI
3. APP安全機制(三)—— Base64加密
4. APP安全機制(四)—— MD5加密
5. APP安全機制(五)—— 對稱加密
6. APP安全機制(六)—— 非對稱加密
7. APP安全機制(七)—— SHA加密
8. APP安全機制(八)—— 偏好設置的加密存儲
開始
首先看一下寫作環境
Swift 4, iOS 11, Xcode 9
軟件開發最重要的一個方面也恰好被認為是最神秘和最可怕的就是應用程序安全性。 用戶希望他們的應用程序正確運行,保護其信息的私密性,并保護該信息免受潛在威脅。
在本教程中,您將深入了解iOS安全性的基礎知識。 您將使用一些基本的加密hashing
方法將用戶輸入安全地存儲在iOS鑰匙串中 - 保護您的用戶數據的私密性和受保護性。
Apple有幾個API可以幫助您保護應用程序的安全,并且您將在使用鑰匙串時探索這些API。 此外,您將使用CryptoSwift
- 一個經過充分審查的開源庫,可實現加密算法。
打開原始工程,看一下示例程序。該示例應用程序允許用戶登錄并查看他們朋友的照片。 大多數應用程序已經為您連接;你的工作是保護應用程序。
打開Friendvatars.xcworkspace
以正確包含所有CocoaPod
依賴項。 構建并運行以查看應用程序打開登錄屏幕:
目前,點擊Sign In
時沒有任何反應。 這是因為沒有辦法保存用戶的憑據。 這就是你要先添加的內容。
Why Security is Important - 為什么安全如此重要
在深入研究代碼之前,您應該了解為什么應用程序中的安全性是必要的。 如果您要存儲私人用戶數據(如電子郵件,密碼或銀行帳戶信息),則應用程序的安全性尤其重要。
為什么Apple如此重視安全性? 從您拍攝的照片到當天所取得的步數,您的iPhone會存儲大量個人數據。 保持這些數據安全非常重要。
誰是iOS生態系統中的攻擊者,他們想要什么? 攻擊者可能是犯罪分子,商業競爭者,甚至是朋友或親戚。 并非所有攻擊者的目的都一樣。 有些人可能想要造成損害或損壞信息,而其他人則可能希望看到他們的生日禮物。
確保應用程序保存的數據免受潛在威脅的侵害是您的職責。 幸運的是,Apple已經構建了許多可以簡化此任務的強大API。
Apple’s Keychain - 蘋果的鑰匙鏈
iOS Keychain
是Apple開發人員最重要的安全元素之一,它是一個用于存儲元數據和敏感信息的專用數據庫。 使用Keychain
是存儲對應用程序至關重要的小塊數據(如機密和密碼)的最佳實踐。
為什么使用Keychain
而不是更簡單的解決方案? 在UserDefaults
中存儲base-64
編碼用戶密碼是不夠的? 當然不夠! 攻擊者恢復以這種方式存儲的密碼是很輕松的。 安全性很難,嘗試自己的自定義解決方案并不是一個好主意。 即使您的應用程序不適用于金融機構,也不應輕易存儲私人用戶輸入。
直接與Keychain
交互很復雜,特別是在Swift
中。 您必須使用主要用C編寫的Security
框架。
幸運的是,您可以通過從Apple的示例代碼GenericKeychain借用Swift包裝器來避免使用這些低級API。 KeychainPasswordItem
為Keychain
提供了一個易于使用的Swift接口,并且已經包含在入門項目中。
是時候深入研究代碼!
Using the Keychain - 使用鑰匙鏈
打開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) 您可以使用用戶輸入的電子郵件和密碼。 如果其中任何一個是零長度,那么您不想繼續。 在實際應用程序中,您還應該在此處向用戶顯示錯誤。
- 3) 您為用戶指定了一個名稱,出于本教程的目的,您可以從設備名稱中獲取該名稱。
注意:您可以通過轉至
System Preferences ? Sharing and changing the computer’s name
來更改Mac的名稱(由SIM
使用)。 此外,您可以轉到Settings ? General ? About ? Name
來更改iPhone的名稱。
現在在signInButtonPressed
中添加以下內容:
signIn()
這會在觸發signInButtonPressed
時調用signIn
方法。
找到textFieldShouldReturn
并將case TextFieldTag.password.rawValue
替換為break
:
signIn()
現在,當用戶點擊鍵盤上的return
而密碼field
具有焦點并包含文本時,將調用signIn()
。
signIn()
尚未完成。 您仍然需要存儲用戶對象以及密碼。 您將在helper
類中實現它。
打開AuthController.swift
,這是一個靜態類,它將保存與此應用程序的身份驗證相關的邏輯。
首先,在isSignedIn
上面的文件頂部添加以下內容:
static let serviceName = "FriendvatarsService"
這定義了將用于在Keychain
中標識應用程序數據的服務名稱。 要使用此常量,請在類的末尾創建一個signIn
方法,如下所示:
class func signIn(_ user: User, password: String) throws {
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(password)
Settings.currentUser = user
}
此方法將用戶的登錄信息安全地存儲在Keychain
中。 它創建一個KeychainPasswordItem
,其中包含您定義的服務名稱以及唯一標識符(account)
。
對于此應用程序,用戶的電子郵件用作Keychain
的標識符,但其他示例可以是唯一的用戶ID或用戶名。 最后,Settings.currentUser
設置為user
- 這存儲在UserDefaults
中。
這種方法不應該被認為是完整的! 直接存儲用戶密碼不是最佳做法。 例如,如果攻擊者破壞了Apple
的Keychain
,他就可以用純文本讀取您用戶的密碼。 更好的解決方案是存儲根據用戶身份構建的哈希。
在AuthController.swift
的頂部,在Foundation
導入下面添加以下內容:
import CryptoSwift
CryptoSwift是用Swift編寫的許多標準加密算法中最受歡迎的集合之一。 密碼學很難,需要正確完成才能有用。 使用流行的庫來實現安全性意味著您無需負責標準化hashing
函數的實現。 最好的加密技術向公眾開放供審查。
注意:Apple的
CommonCrypto
框架為您提供了許多有用的hashing
函數,但在Swift中與它進行交互并不容易。 這就是為什么在本教程中我們選擇了CryptoSwift
庫。
接下來添加以下signIn
:
class func passwordHash(from email: String, password: String) -> String {
let salt = "x4vV8bGgqqmQwgCoyXFQj+(o.nUNQhVP7ND"
return "\(password).\(email).\(salt)".sha256()
}
此方法接受電子郵件和密碼,并返回hashed
字符串。 salt
是一個唯一的字符串,用于制作通用密碼,這種情況并不常見。 sha256(
)是一個CryptoSwift
方法,它在輸入字符串上完成一種SHA-2哈希。
在前面的示例中,攻擊者破壞了Keychain
會發現此哈希值。 攻擊者可能會創建一個常用密碼表及其哈希值來與此哈希值進行比較。 如果您在沒有salting
的情況下僅對用戶的輸入進行了哈希處理,并且密碼存在于攻擊者哈希表中,則密碼將受到損害。
加入鹽會增加攻擊的復雜性。 此外,您將用戶的電子郵件和密碼與salt相結合,以創建一個不容易破解的哈希。
注意:對于使用服務器后端的身份驗證,應用和服務器將共享相同的
salt
。 這允許他們以相同的方式構建哈希并比較兩個哈希來驗證身份。
回到signIn(_:password :)
,用這個替換調用savePassword
的行:
let finalHash = passwordHash(from: user.email, password: password)
try KeychainPasswordItem(service: serviceName, account: user.email).savePassword(finalHash)
signIn
現在存儲強哈希,而不是原始密碼。 現在是時候將它添加到視圖控制器了。
返回Auth ViewController.swift
并將以下內容添加到signIn()
的底部:
do {
try AuthController.signIn(user, password: password)
} catch {
print("Error signing in: \(error.localizedDescription)")
}
雖然這將存儲user
并保存hashed
密碼,但是應用程序登錄需要更多.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
中的當前用戶。 如果沒有用戶,則沒有用于從Keychain
查找密碼哈希的標識符,因此您表明他們未登錄。 - 2) 您從
Keychain
讀取密碼哈希,如果密碼存在且不為空,則認為用戶已登錄。
現在AppController.swift
中的handleAuthState
可以正常工作,但是在登錄后需要新的應用程序啟動才能正確更新UI。 相反,通知應用程序狀態更改(例如身份驗證)的好方法是通過通知。
將以下內容添加到AuthController.swift
的底部:
extension Notification.Name {
static let loginStatusChanged = Notification.Name("com.razeware.auth.changed")
}
在編寫自定義通知時使用反向域標識符是一種很好的做法,通常從應用程序的包標識符派生。 使用唯一標識符可以在調試時提供幫助,因此與您的通知相關的任何內容都會與日志中提到的其他框架完美區別開來。要使用此自定義通知名稱,請將以下內容添加到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
。
Build并運行。 使用任何電子郵件和密碼組合登錄后,您將看到朋友列表:
你會注意到沒有任何頭像,只有朋友的名字。 這看起來不太令人愉快。 您可能應該退出并忘記這個未完成的應用程序。 哦,來吧,即使退出按鈕也不起作用。
登錄效果很好,但沒有辦法退出應用程序。 這實際上很容易實現,因為有一個通知會發出任何身份驗證狀態更改的信號。
返回到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) 您檢查是否存儲了當前用戶,如果沒有,則提前
return
。 - 2) 您從
Keychain
中刪除密碼哈希。 - 3) 您清除用戶對象并發布通知。
要連接它,跳轉到FriendsViewController.swift
并將以下內容添加到當前為空的signOut
:
try? AuthController.signOut()
調用新方法以在選擇Sign Out
按鈕時清除已登錄用戶的數據。
處理應用程序中的錯誤是一個好主意,但是為了本教程,您將忽略任何錯誤。
Build并運行,然后點擊Sign Out
按鈕。
現在您有一個在應用程序中工作的身份驗證的完整示例!
Hashing - 哈希
您在設置身份驗證方面做得非常好!然而,樂趣尚未結束。現在,您將在朋友視圖中的名稱前面找到該空白區域。
在FriendsViewController.swift
中,顯示了一個User
模型對象列表。您還想在視圖中顯示每個用戶的頭像圖像。由于User
只有兩個屬性,一個名稱和電子郵件,您應該如何顯示圖像?
事實證明,有一項服務需要一個電子郵件地址并將其與頭像圖像聯系起來:Gravatar!如果您之前沒有聽說過Gravatar
,它通常用于博客和論壇,以便將電子郵件地址與頭像全局關聯。這簡化了事情,因此用戶無需將新頭像上傳到他們加入的每個論壇或網站。
這些用戶中的每一個都具有與其電子郵件相關聯的頭像。因此,您唯一需要做的就是向Gravatar
提出請求并獲取他們的圖像。為此,您將創建其電子郵件的MD5
哈希以構建請求URL。
如果您查看Gravatar’s site上的文檔,您會發現需要一個哈希的電子郵件地址來構建請求。這將是一塊蛋糕,因為你可以利用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) 您在
table view
中重新加載行,以便顯示頭像圖像。
Build并運行。 現在您可以查看朋友的頭像圖像和名稱:
注意:如果您的電子郵件返回藍色G上的默認白色,請轉到
Gravatar
的網站并上傳您自己的頭像并加入您的朋友!
您現在擁有一個完整的應用程序處理基本的iOS安全和身份驗證,您可以查看由Gravatar
驅動的頭像。 您了解了安全性的重要性,關于iOS鑰匙串和一些最佳實踐,如存儲哈希而不是純文本值。 希望你也很開心學習這個!
后記
本篇主要講述了基本iOS安全之鑰匙鏈和哈希,感興趣的給個贊或者關注~~~