您好,登錄后才能下訂單哦!
iOS的keychain可以說是系統里唯一可以做到安全可靠存儲應用敏感數據并且可以在應用卸載或重新安裝時仍然保留其數據的地方。當使用itunes進行數據備份時,每個應用程序在keychain里的數據都會得到備份,而且備份的數據是經過加密的,所以無論數據存儲在哪里,都十分安全。鑒于keychain的這些特性,使得它成為開發者存儲應用敏感數據的首選,應用程序常見的敏感數據通常有密碼,秘鑰等等。
然而,開發者面臨的一個問題是,iOS sdk里提供的keychain接口十分隱晦難用,初學者在理解及應用上遇到了不小的麻煩,甚至Apple官方提供的keychain示例代碼GenericKeychain也都十分難讀懂。因此,本文試圖將keychain的使用進行一下梳理,讓初學者能夠了解其基本使用。
準備工作
準備使用keychain接口前,首先需要在xcode里加入Security.framework,然后在代碼里加入頭文件 "#import <Security/Security.h>"。注意,如果你使用的是object-c,Security.framework里的接口都是C語言風格接口而不是object-c語言風格的接口。此外,Security.framework只工作在實際設備上,無法在模擬器上使用。
Keychain概覽
Keychain里可以存儲若干條目(item),每個條目都屬于某一個類別(class),以下是常見的幾種類別:
kSecClassInternetPassword
屬于該類別的條目往往用來存儲上網登錄密碼,遠程服務器密碼等
kSecClassGenericPassword
存儲一些通用的密碼,比如數據庫密碼,***連接的密碼等等
kSecClassCertificate, kSecClassKey和kSecClassIdentity
這三類條目往往用于建立基于證書,秘鑰和公鑰系統的安全連接。
條目類別是一個條目最基本的屬性,每個存入keychain的條目,都需要為它制定一個類別。
操作keychain常見的3個方法:
SecItemAdd
添加新條目到keychain.
SecItemUpdate
修改一個已存在keychain里的條目
SecItemCopyMatching
查詢keychain里的條目并且讀取條目信息
下面用一張摘取自蘋果官方網站的一張圖闡述一下一個常見keychain的操作流程,該圖以一個應用連接ftp服務器為例
在上圖中,應用大概流程是這樣的:應用開始連接ftp服務器,首先查詢keychain是否存在服務器密碼,如果存在,那么直接取出密碼登陸;如果不存在,那么應用彈出對話框要求用戶輸入,然后進行登陸,如果登陸成功,那么存儲密碼到keychain。
Keychain接口參數
所有對keychain接口的操作,參數的傳遞基本上都用到字典,將所需要的參數放入字典,然后將字典傳遞給keychain接口。上面提到的條目類別,就是一個參數,除此之外,操作條目往往還需要條目ID,條目所屬服務名,條目所屬賬戶名等。這些參數的名稱如下:
kSecClass:條目類別
kSecAttrGeneric:條目id
kSecAttrService:條目所屬服務
kSecAttrAccount:條目所屬賬戶名
其中kSecAttrService和kSecAttrAccount在整個keychain里必須唯一,不能重名。
Keychain條目查詢
使用SecItemCopyMatching進行查詢,查詢時,我們需要指明要查詢條目的類別(kSecClass),條目的id(kSecAttrGeneric),條目所屬的服務和賬戶(kSecAttrService,kSecAttrAccount)。此外,我們還可以設置其他一些查詢條件,比如返回條目的數量(kSecMatchLimit),返回條目的數據類型,比如:
kSecReturnData:返回條目所存儲的數據,返回值類型是CFDataRef
kSecReturnAttributes:返回該條目的屬性,返回值是字典類型CFDictionaryRef
kSecReturnRef:返回條目的引用,根據條目所屬類別,返回值類型可能是:SecKeychainItemRef, SecKeyRef,SecCertificateRef, SecIdentityRef.
kSecReturnPersistentRef:返回條目的引用,返回值類型是CFDataRef
如下代碼示例用來查詢存儲在keychain里的密碼
//建立詞典,用來傳遞參數 NSMutableDictionary *dictionary = [[NSMutableDictionary dictionary]; //設置條目類別,我們用該條目存儲普通密碼,所以設置成kSecClassGenericPassword [dictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; //設置條目的id,比如“MyPasswordForFtp",條目id必須時NSDate,而不是NSString NSString *itemIDString = @"MyPasswordForFtp"; NSData *itemID= [itemIDString dataUsingEncoding:NSUTF8StringEncoding]; [dictionary setObject:itemIDforKey:(id)kSecAttrGeneric]; //設置條目所屬的服務和賬戶,為了避免重名,我們使用常見的反轉域名規則,比如com.mykeychain.ftppassword NSString *account = @"com.mykeychain.ftppassword"; NSString *service = @"com.mykeychain.ftppassword"; [dictionary setObject:account forKey:(id)kSecAttrAccount]; [dictionary setObject:service forKey:(id)kSecAttrService]; //設置查詢條件,只返回一個條目 [dictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; //設置查詢條件,返回條目存儲的數據 (kSecReturnData == True) [dictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; //開始查詢 NSData *result = nil; OSStatus status = SecItemCopyMatching((CFDictionaryRef)dictionary , (CFTypeRef *)&result);
Keychain條目添加
使用SecItemAdd()進行條目添加,我們需要指明新條目的類別(kSecClass),新條目的id(kSecAttrGeneric),新條目所屬的服務和賬戶(kSecAttrService,kSecAttrAccount),此外,還需要指明該條目所存儲的數據(kSecValueData),否則,沒有數據的條目就沒有任何存入keychain的意義了。
請看如下代碼示例:
//建立詞典,用來傳遞參數 NSMutableDictionary *dictionary = [[NSMutableDictionary dictionary]; //設置條目類別,我們用該條目存儲普通密碼,所以設置成kSecClassGenericPassword [dictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; //設置條目的id,比如“MyPasswordForFtp",條目id必須時NSDate,而不是NSString NSString *itemIDString = @"MyPasswordForFtp"; NSData *itemID= [itemIDString dataUsingEncoding:NSUTF8StringEncoding]; [dictionary setObject:itemIDforKey:(id)kSecAttrGeneric]; //設置條目所屬的服務和賬戶,為了避免重名,我們使用常見的反轉域名規則,比如com.mykeychain.ftppassword NSString *account = @"com.mykeychain.ftppassword"; NSString *service = @"com.mykeychain.ftppassword"; [dictionary setObject:account forKey:(id)kSecAttrAccount]; [dictionary setObject:service forKey:(id)kSecAttrService]; //設置條目數據,條目數據時NSDate NSString *password = @"123456"; NSData *itemData = [password dataUsingEncoding:NSUTF8StringEncoding]; [dictionary setObject:itemData forKey:(id)kSecValueData]; //添加條目到keychain OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
keychain條目的修改
使用SecItemUpdate()進行條目修改,需要傳入兩個詞典,第一個詞典用來查詢條目,第二個詞典用來傳遞修改后的新值。查詢條目所用的詞典設置請看上述Keychain條目查詢章節,更新值所用詞典里設置要更新的參數和值。這里請注意的是,查詢詞典里不能設置任何查詢條件,比如kSecMatchLimit和kSecReturnData,kSecReturnAttributes等,否則會出錯。因為這里的查詢是為了更新數據,是以寫入為目的,不是真正的查詢和讀取條目,所以設置這些和讀取有關的條件是無意義的,系統會認為是和安全有關的錯誤,會返回-50(errSecParam)
下面代碼用來更新密碼,要更新的條目id為“MyPasswordForFtp”的條目
//建立查詢字典 NSMutableDictionary *searchDictionary = [[NSMutableDictionary dictionary]; //設置查詢字典 [searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; NSString *itemIDString = @"MyPasswordForFtp"; NSData *itemID= [itemIDString dataUsingEncoding:NSUTF8StringEncoding]; [searchDictionary setObject:itemIDforKey:(id)kSecAttrGeneric]; NSString *account = @"com.mykeychain.ftppassword"; NSString *service = @"com.mykeychain.ftppassword"; [searchDictionary setObject:account forKey:(id)kSecAttrAccount]; [searchDictionary setObject:service forKey:(id)kSecAttrService]; // 建立更新字典 NSMutableDictionary *updateDictionary = [[NSMutableDictionary dictionary]; // 設置要更新的新密碼 NSString* newPassword = @"654321"; NSData *passwordData = [newPassword dataUsingEncoding:NSUTF8StringEncoding]; [updateDictionary setObject:passwordData forKey:(id)kSecValueData]; //進行更新 OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary, (CFDictionaryRef)updateDictionary);
keychain接口常見錯誤
SecItemUpdate 返回-50(errSecParam):請檢查查詢詞典里是否存在讀取條件,比如kSecReturnData,kSecMatchLimit等
SecItemAdd 返回-25299 (errSecDuplicateItem: 請檢查kSecAttrAccount和kSecAttrService是否已經存在于keychain中,請嘗試設置其他值避免重復
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。