iOS 鑰匙串的基本使用
級別: ★☆☆☆☆
標籤:「鑰匙串」「keychain」「iOS」
作者: WYW
審校:QiShare團隊
前言 :
專案中有時會需要儲存敏感資訊(如密碼、金鑰等),蘋果官方提供了一種儲存機制-- 鑰匙串(keychain)
。
keychain是一種儲存在 硬碟
上的 加密的資料庫
。這個可能是解除安裝App後,keychain資訊還在的原因。
keychain適合儲存 較小的資料量
( 不超過上千位元組或上兆位元組
)的內容。
筆者做了一個關於keychain的 增、刪、改、查
的Demo(QiKeychain),給大家介紹下keychain的基本使用。
下圖(確保keychain中使用者的資訊保安)有利於我們直觀瞭解keychain。

Demo(QiKeychain)解讀:
筆者用Demo(QiKeychain)做了4件事。
- 增加:儲存使用者名稱、密碼到keychain;
- 查詢:根據使用者名稱從keychain中查詢密碼;
- 刪除:從keychain中刪除使用者名稱、密碼等相應資訊;
- 修改:修改keychain中的使用者名稱對應的密碼;
Demo(QiKeychain)對keychain的操作效果如下:
- 儲存使用者名稱 “QiShare”,密碼:1234;
- 查詢使用者名稱為“QiShare”的密碼,顯示密碼為:1234;
- 修改使用者名稱“QiShare”的密碼為“123456”;
- 查詢“QiShare”的密碼,顯示為“123456”;
- 把“QiShare”從keychain中刪除。

keychain基本使用API
keychain有四個常用的API,用於增、刪、改、查keychain中的資料。
keychain中的資料子項是以item的形式存在的。
舉個例子:就儲存使用者名稱、密碼的情景來說,每個item包含儲存的使用者名稱和密碼及其他屬性資訊,keychain中包含多個使用者名稱和密碼的item。
下圖(把資料和屬性儲存到keychain中)利於我們理解儲存過程

SecItemAdd:新增一個item或多個items到keychain
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result) API_AVAILABLE(macos(10.6), ios(2.0)); 複製程式碼
儲存關鍵程式碼:
NSDictionary *saveSecItems = @{(id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrService: service, (id)kSecAttrAccount: account, (id)kSecValueData: passwordData }; OSStatus saveStatus = SecItemAdd((CFDictionaryRef)saveSecItems, NULL); 複製程式碼
SecItemCopyMatching:返回匹配搜尋查詢的一個item或多個items
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result) API_AVAILABLE(macos(10.6), ios(2.0)); 複製程式碼
查詢關鍵程式碼:
NSDictionary *matchSecItems = @{ (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrService: service, (id)kSecAttrAccount: account, (id)kSecMatchLimit: (id)kSecMatchLimitOne, (id)kSecReturnData: @(YES) }; CFTypeRef dataRef = nil; OSStatus errorCode = SecItemCopyMatching((CFDictionaryRef)matchSecItems, (CFTypeRef *)&dataRef); 複製程式碼
SecItemUpdate:修改匹配搜尋查詢的一個item或多個items
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) API_AVAILABLE(macos(10.6), ios(2.0)); 複製程式碼
注意:更新程式碼這部分,筆者開始的時候遇到一些問題,還要多謝組內成員,尤其是昆哥的指教。
SecItemUpdate接收了2個引數,query和attributesToUpdate。
第一個引數query用於查詢到相應的item, 第二個引數attributesToUpdate用於傳入要更新的資訊。
筆者曾錯誤地給第二個引數attributesToUpdate傳入過(id)kSecClass: (id)kSecClassGenericPassword要更改的內容。
結果報錯為:
errSecNoSuchAttr = -25303, /* The specified attribute does not exist. */
更新關鍵程式碼:
NSDictionary *queryItems = @{(id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrService: service, (id)kSecAttrAccount: account }; NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *updatedItems = @{ (id)kSecValueData: passwordData, }; OSStatus updateStatus = SecItemUpdate((CFDictionaryRef)queryItems, (CFDictionaryRef)updatedItems); 複製程式碼
SecItemDelete:刪除匹配搜尋查詢的一個item或多個items
OSStatus SecItemDelete(CFDictionaryRef query) API_AVAILABLE(macos(10.6), ios(2.0)); 複製程式碼
刪除關鍵程式碼:
NSDictionary *deleteSecItems = @{ (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrService: service, (id)kSecAttrAccount: account }; OSStatus errorCode = SecItemDelete((CFDictionaryRef)deleteSecItems); 複製程式碼
顯然keychain的 增刪改查
相關的API都需要設定相應的 屬性字典
(分別代指上述的saveSecItems 、matchSecItems 、queryItems 、updatedItems 、deleteSecItems)
- 屬性字典的key、value常用的有:(這部分內容讀者也可直接看文件)
- (id)kSecClass: (id)kSecClassGenericPassword kSecClass表示item的class (id)kSecClass的值表明一個通用的密碼item筆者一般都傳入kSecClassGenericPassword
- (id)kSecAttrService: service kSecAttrService的value用於表明item的service
- (id)kSecAttrAccount: account (id)kSecAttrAccoun的值表明item的帳戶名
- (id)kSecValueData: passwordData (id)kSecValueData表示item的資料
- (id)kSecMatchLimit: (id)kSecMatchLimitOne, (id)kSecMatchLimit 有2個值(id)kSecMatchLimitOne、和(id)kSecMatchLimitAll kSecMatchLimitOne:表示只匹配第一個符合條件的item kSecMatchLimitAll:表示匹配不限數量的items
- (id)kSecReturnData: @(YES) (id)kSecReturnData的值是一個Boolean型別的值用於確定是否返回item data
- kSecClass的值表示item的class kSecClass的值表明一個通用的密碼item筆者一般都傳入的kSecClassGenericPassword
- kSecClass的值表示item的class kSecClass的值表明一個通用的密碼item筆者一般都傳入的kSecClassGenericPassword
Demo(QiKeychain)相關程式碼
在Demo(QiKeychain)中,筆者對keychain相關使用的API進行了封裝。獲取 Demo(QiKeychain) GitHub地址: QiKeychain 。
注意:
筆者後來封裝的程式碼,修改了儲存操作的邏輯。
修改內容為: 在儲存使用者名稱密碼的時候
-> 先在keychain中 查詢
使用者名稱是否存在
-> 若存在,就進行 更新
操作;
-> 若不存在就進行 儲存
操作。
相應示意圖(使用鑰匙串儲存網路密碼)如下:
