iOS Keychain(鑰匙串) 原理和APP之間共享信息(例如:賬號(hào)密碼)

Keychain 介紹

英文好的點(diǎn)這官方文檔

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文件

輸入圖片說(shuō)明

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
輸入圖片說(shuō)明

(AppIdentifierPrefix)可以是開(kāi)發(fā)者的代號(hào)需要登錄才會(huì)有,也就是開(kāi)發(fā)者證書(shū)后小括號(hào)的內(nèi)的英文數(shù)字組合。使用$(AppIdentifierPrefix)只能被同一個(gè)開(kāi)發(fā)者賬號(hào)的app來(lái)存取,以防被有心人盜取。
輸入圖片說(shuō)明

若其他的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ō)明

輸入圖片說(shuō)明

官方sampleCode

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容