Keychain 介紹
Keychain Services 是 macOS 和 iOS 都提供一種安全地存儲(chǔ)敏感信息的工具,比如,網(wǎng)絡(luò)密碼:用于保存訪問(wèn)服務(wù)器或者網(wǎng)站,通用密碼:用來(lái)保存應(yīng)用程序或者數(shù)據(jù)庫(kù)密碼.與此同時(shí),用于認(rèn)證的證書(shū),密鑰,和身份信息,也可以存儲(chǔ)在Keychain中.Keychain Services 的安全機(jī)制保證了存儲(chǔ)這些敏感信息不會(huì)被竊取。簡(jiǎn)單說(shuō)來(lái),Keychain 就是一個(gè)安全容器。 PS:在iOS中keychian 依賴用于簽名的provisioning profile描述文件,確保發(fā)布不同版本的時(shí)候使用同一個(gè)pp文件

Keychain 的結(jié)構(gòu)
Keychain 可以包含任意數(shù)量的 keychain item。每一個(gè) keychain item 包含數(shù)據(jù)和一組屬性。對(duì)于一個(gè)需要保護(hù)的 keychain item,比如密碼或者私鑰(用于加密或者解密的string字節(jié))數(shù)據(jù)是加密的,會(huì)被 keychain 保護(hù)起來(lái)的;對(duì)于無(wú)需保護(hù)的 keychain item,例如,證書(shū),數(shù)據(jù)未被加密。
跟keychain item有關(guān)系的取決于item的類型;應(yīng)用程序中最常用的是網(wǎng)絡(luò)密碼(Internet passwrods)和普通的密碼。正如你所想的,網(wǎng)絡(luò)密碼像安全域(security domain)、協(xié)議、和路徑等一些屬性。在macOS中,當(dāng)keychain被鎖的時(shí)候加密的item沒(méi)辦法訪問(wèn),如果你想要該問(wèn)被鎖的item,就會(huì)彈出一個(gè)對(duì)話框,需要你輸入對(duì)應(yīng)keychain的密碼。當(dāng)然,未有密碼的keychain你可以隨時(shí)訪問(wèn)。但在iOS中,你只可以訪問(wèn)你自已的keychain items;
Keychain的特點(diǎn)
數(shù)據(jù)并不存放在App的Sanbox中,即使刪除了App,資料依然保存在keychain中。如果重新安裝了app,還可以從keychain獲取數(shù)據(jù)。
keychain的數(shù)據(jù)可以用過(guò)group方式,讓程序可以在App間共享。不過(guò)得要相同TeamID
keychain的數(shù)據(jù)是經(jīng)過(guò)加密的 ##Keychain的使用 大多數(shù)iOS應(yīng)用需要用到Keychain, 都用來(lái)添加一個(gè)密碼,修改一個(gè)已存在Keychain item或者取回密碼。Keychain提供了以下的操作
SecItemAdd 添加一個(gè)item
SecItemUpdate 更新已存在的item
SecItemCopyMatching 搜索一個(gè)已存在的item
SecItemDelete 刪除一個(gè)keychain item
讀取
func readPassword() throws -> String {
/*
查找 對(duì)應(yīng) service, account and
access group 的keychainItem.
*/
var query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnAttributes as String] = kCFBooleanTrue
query[kSecReturnData as String] = kCFBooleanTrue
// 查找已經(jīng)存在的item
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
// 檢查插入或更新是否成功
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
// 讀取item中的密碼信息
guard let existingItem = queryResult as? [String : AnyObject],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8)
else {
throw KeychainError.unexpectedPasswordData
}
return password
}
寫(xiě)入
func savePassword(_ password: String) throws {
// 編碼Encoding->data
let encodedPassword = password.data(using: String.Encoding.utf8)!
do {
// 查找是否存在item.
try _ = readPassword()
// 如果存在更新item中的密碼
var attributesToUpdate = [String : AnyObject]()
attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject?
let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
}
catch KeychainError.noPassword {
/*
如果不存在 創(chuàng)建一個(gè)dictionary 作為item 存入keychain
*/
var newItem = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
newItem[kSecValueData as String] = encodedPassword as AnyObject?
let status = SecItemAdd(newItem as CFDictionary, nil)
guard status == noErr else { throw KeychainError.unhandledError(status: status) }
}
}
關(guān)于共享 Keychain的數(shù)據(jù)可以透過(guò)Group Access的方式,讓資料可以在App間共享,Google系列的App (Gmail、Google+、日歷…)就是通過(guò)這樣的方式來(lái)記錄使用者登入信息,只要使用者在其中一個(gè)App中完成登入了,其他的App也可以讀取到同相的登入咨詢進(jìn)行登錄。 ###進(jìn)入Capabilities,將Keychain打開(kāi)
輸入圖片說(shuō)明
開(kāi)啟Keychain后,會(huì)自動(dòng)新增一個(gè)Keychain Group,使用的是Bundle Identifier。 同時(shí)也會(huì)自動(dòng)新增一個(gè)entitlements文件,里面也會(huì)有一個(gè)Access Group,名為 $(AppIdentifierPrefix)+你的bundleID 


若其他的App也要存取當(dāng)前Keychain的數(shù)據(jù),就必需在Keychain開(kāi)啟后,新增相同的Keychain Group (Access Group會(huì)根據(jù)Keychain Group自動(dòng)新增)。 PS: 需要Info.plist新增一對(duì)鍵值 Key: AppIdentifierPrefix Value: $(AppIdentifierPrefix) 方便取得 Identifier Prefix
最終效果
輸入圖片說(shuō)明

