iOS筆記: 使用Keychain在App間共享資料

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

發表留言