文件保護用來保護**數據**,而鑰匙串則用來保護**祕密**。在這裏,祕密是指用來訪問其他數據的一小段數據。最常見的祕密就是密碼和私鑰了。
鑰匙串由操作系統保護,在設備鎖定時會進行加密處理。實際上,它的工作原理跟文件保護很像。不幸的是,Keychain API並不友好,所以許多開發人員爲Keychain API做了一些包裝。不過,筆者推薦使用的是蘋果GenericKeychain
示例代碼中的KeyChainItemWrapper
。本節將在簡要介紹底層的數據結構之後介紹它。
KeyChainItemWrapper
有一些問題,其中最突出的問題就是它並不兼容ARC。可以將KeyChainItemWrapper.m文件的ARC關掉,或者增加需要的__bridge
類型轉換。在網絡上搜索GenericKeyChain ARC,可以找到一些做過這方面工作的人寫的文章。它並不像我們想的那樣方便。有若干種方法都能用來完成這個任務。雖然這些方法都還不成熟,尚不足在這裏推薦,但一切都在改進。關注一下iosptl.com上的更新。
鑰匙串中的條目稱爲SecItem
,但它是存儲在CFDictionary
中的。SecItemRef
類型並不存在。SecItem
有五類:通用密碼、互聯網密碼、證書、密鑰和身份。在大多數情況下,我們用到的都是通用密碼。許多問題都是開發人員嘗試用互聯網密碼造成的。互聯網密碼要複雜得多,而且相比之下優勢寥寥無幾,除非開發Web瀏覽器,否則沒必要用它。KeyChainItemWrapper
只使用通用密碼,這也是我喜歡它的原因之一。iOS應用很少將密鑰和身份存儲起來,所以我們在本書中不會討論這方面的內容。只有公鑰的證書通常應該存儲在文件中,而不是鑰匙串中。
最後,我們需要在鑰匙串中搜索需要的內容。密鑰有很多個部分可用來搜索,但最好的辦法是將自己的標識符賦給它,然後搜索。通用密碼條目都包含屬性kSecAttrGeneric
,可以用它來存儲標識符。這也是KeyChainItemWrapper
的處理方式。
鑰匙串中的條目都有幾個可搜索的**屬性**和一個加密過的**值**。對於通用密碼條目,比較重要的屬性有賬戶(kSecAttrAccount
)、服務(kSecAttrService
)和標識符(kSecAttrGeneric
)。而值通常是密碼。
有了這個背景,現在我們可以看看如何使用KeyChainItemWrapper
了。首先,如下代碼所示,可以用initWithIdentifier:accessGroup:
來創建一個密碼。稍後介紹訪問組的概念,先將它保留爲nil
。
1 2 3 4 |
KeychainItemWrapper *
wrapper = [[KeychainItemWrapper alloc]
initWithIdentifier:@"MyKeychainItem"
accessGroup:nil];
|
現在可以對wrapper
進行讀取和寫入了,就跟使用NSDictionary
一樣。它會自動跟鑰匙串同步。__bridge
強制轉換用來將Core Foundation的常量傳給一個使用ARC的Cocoa方法。
1 2 3 4 |
id kUsernameKey = (__bridge id)kSecAttrAccount;
id kPasswordKey = (__bridge id)kSecValueData;
NSString *username = [wrapper objectForKey:kUsernameKey];
[wrapper setObject:password forKey:kPasswordKey];
|
注意,我使用的是個Core Foundation類型(kSecAttrAccount
),這裏它需要用到id
(objectForKey:
),不要用類型轉換,也不要用__bridge
。這是一個新特性。
KeyChainItemWrapper
會緩存讀入數據,但不會緩存寫出的數據。向鑰匙串中寫數據的成本很高,所以我們不會頻繁寫入。鑰匙串不適合用來存儲經常改變的敏感數據。那類數據應該保存到加密文件中,可以參考15.3節的內容。