Keychain是iOS所提供的一個安全儲存參數的方式,最常用來當作儲存帳號、密碼、信用卡資料等需要保密的資訊,Keychain會以加密的方式將這些資訊儲存於裝置當中。
由於Keychain的資料並不是儲存在App的Sandbox中,所以即使將App從裝置中刪除了,這些資料還是存在於裝置中,當使用者重新安裝了相同的App後,這些資訊還是可以被取得。
另一個特色是,Keychain的資料可以透過Group Access的方式,讓資料可以在App間共享,Google系列的App (Gmail、Google+、日曆…)就是透過這樣的方式來紀錄使用者登入資訊,只要使用者在其中一個App中完成登入了,其他的App也可以讀取到同相的登入資訊進行登入。
本文將針對App間透過Keychain共享資料進行介紹與實作。
範例程式: Github
開啟Keychain功能
進入Capabilities設定中,將Keychain開啟
開啟Keychain後,會自動新增一個Keychain Group,使用的是Bundle Identifier。
例: com.8085studio.iak
同時也會新增一個entitlements檔,裡面也會有一個Access Group,名稱為
$(AppIdentifierPrefix)com.8085studio.iak
$(AppIdentifierPrefix)
可以說是開發者的發佈代號,可以在開發人員控制台中查詢到。使用$(AppIdentifierPrefix)
的用意要讓這些資料只能被同一個開發帳號所發佈的App來存取,以防被有心人盜取。
若其他的App也要存取這支App裡的Keychain資料,就必需在Keychain開啟後,新增相同的Keychain Group (Access Group會根據Keychain Group自動新增)。
Keychain存取工具
首先撰寫一個用來存取Keychain的工具,操作Keychain有點類似資料庫,主要包括4個元素:
- Service: 可以視作目標資料庫,此為前文所提的 Keychain Group
- Access Group: 此為前文所提的 Access Group
- Account: 作為Hashtable 的 KEY值
- Data Value: 作為KEY值相對應的VALUE
private var service = "" private var group = "" init(service: String, group: String) { super.init() self.service = service self.group = group } private func prepareDic(key: String) -> [String: AnyObject] { var dic = [String: AnyObject]() dic[kSecClass as String] = kSecClassGenericPassword dic[kSecAttrService as String] = self.service dic[kSecAttrAccessGroup as String] = self.group dic[kSecAttrAccount as String] = key return dic } func insert(data: NSData, key: String) -> Bool { var dic = prepareDic(key) dic[kSecValueData as String] = data let status = SecItemAdd(dic, nil) if status == errSecSuccess { return true } else { return false } } func query(key: String) -> NSData? { var dic = prepareDic(key) dic[kSecReturnData as String] = kCFBooleanTrue var data: AnyObject? let status = withUnsafeMutablePointer(&data) { SecItemCopyMatching(dic, UnsafeMutablePointer($0)) } if status == errSecSuccess { return data as? NSData } else { return nil } } func update(data: NSData, key: String) -> Bool { var query = prepareDic(key) var dic = [String: AnyObject]() dic[kSecValueData as String] = data let status = SecItemUpdate(query, dic) if status == errSecSuccess { return true } else { return false } } func deleteData(key: String) -> Bool { var dic = prepareDic(key) let status = SecItemDelete(dic) if status == errSecSuccess { return true } else { return false } }
Keychain工具使用service與access group來初始化,之後就可以透過insert (新增)、query (查詢)、update (更新)、deleteData (刪除),來對於key-value進行存取。
App間Keychain資料共享
為了方便示範實作,範例程式直接複制了原本的Target並將Bundle Identifier設為不同於原本的Target,這樣就會產生兩支不同的App,並實作Keychain資料共享。
Bundler Identifier分別為
com.8085studio.iak1 com.8085studio.iak2
開啟Keychain功能並將Keychain Group都設為
com.8085studio.iak1
所以在兩個Target的entitlementst檔裡都會有相同的Access Group
$(AppIdentifierPrefix)com.8085studio.iak1
接著再使用 KeychainUtility.swift
就可以在兩個App之間共享相同的Keychain資料了
Note: 範例程式中在Info.plist裡新增了一組key-value
Key: AppIdentifierPrefix
Value: $(AppIdentifierPrefix)
方便在runtime時取得App Identifier Prefix
範例程式請至以下網址下載
Github