這5個類型只是對應於不一樣的item,存儲的屬性有區別,使用上都是同樣的。數據庫
不一樣類型對應的屬性:數組
既然蘋果是採用SQLite去存儲的,那麼以上這些不一樣item的attribute能夠理解是數據庫裏面表的字段。那麼對keychain的操做其實也就是普通數據庫的增刪改查了。這樣也許就會以爲那些API也沒那麼難用了。安全
NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; CFErrorRef error = NULL; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
以這個添加kSecClassGenericPassword item爲例,在字典裏面咱們設置瞭如下幾個屬性:獲取權限爲當設備處於未鎖屏狀態,item的類型爲kSecClassGenericPassword,item的value爲@"123456", item的帳戶名爲@"account name", item的service爲@"loginPassword"。最後,調用SecItemAdd進行插入。使用上有點像CoreData。服務器
NSDictionary *query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : @"loginPassword", (__bridge id)kSecAttrAccount : @"account name" }; OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
刪除一樣也是指定以前存的item的屬性,最後調用SecItemDelete這個方法。這邊要注意的是勁量用多個字段肯定這個item,(雖然日常開發均可能是惟一)防止刪除了其餘item;好比咱們把kSecAttrAccount這個屬性去掉,那麼將會刪除全部的kSecAttrService對應value爲@"loginPassword"的item;app
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; NSDictionary *update = @{ (__bridge id)kSecValueData : [@"654321" dataUsingEncoding:NSUTF8StringEncoding], }; OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
蘋果推薦咱們用SecItemUpdate去修改一個已經存在的item,可能咱們喜歡先調用SecItemDelete方法去刪除,再添加一個新的。這個主要目的是防止新添的item丟失了原來的部分屬性。這個方法須要兩個入參,一個字典是用來指定要更新的item,另外一個字典是想要更新的某個屬性的value,最後調用SecItemUpdate。框架
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSString *pwd = [[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(dataTypeRef) encoding:NSUTF8StringEncoding]; NSLog(@"==result:%@", pwd); }
查和前面幾個操做相似,首先一樣是指定屬性定位到這個item,最後調用SecItemCopyMatching方法。既然是數據庫查詢,確定會有記錄的條數的問題。本例中使用了kSecMatchLimitOne,表示返回結果集的第一個,固然這個也是默認的。若是是查詢出多個,kSecMatchLimitAll可使用這個,那麼返回的將是個數組。SecItemCopyMatching方法的入參dataTypeRef,是一個返回結果的引用,會根據不一樣的item,返回對應不一樣的類型(如NSCFData, NSCFDictionary, NSCFArray等等)。async
剛剛上面是返回存儲的value的引用,若是咱們想看看這個item全部的屬性怎麼辦?咱們可使用kSecReturnRefide
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnRef : @YES, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"noraml", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSDictionary *dict = (__bridge NSDictionary *)dataTypeRef; NSString *acccount = dict[(id)kSecAttrAccount]; NSData *data = dict[(id)kSecValueData]; NSString *pwd = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *service = dict[(id)kSecAttrService]; NSLog(@"==result:%@", dict); }
這樣,咱們就獲得了這個item的全部屬性。測試
同一個開發者帳號下(teamID),各個應用之間能夠共享item。keychain經過keychain-access-groups
來進行訪問權限的控制。在Xcode的Capabilities選項中打開Keychain Sharing便可。ui
每一個group命名開頭必須是開發者帳號的teamId。不一樣開發者帳號的teamId是惟一的,因此蘋果限制了只有同一個開發者帳號下的應用才能夠進行共享。若是有多個sharedGroup,在添加的時候若是不指定,默認是第一個group。
添加:
NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test", (__bridge id)kSecAttrService : @"noraml1", (__bridge id)kSecAttrSynchronizable : @YES, }; CFErrorRef error = NULL; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
取:
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnRef : @YES, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test", (__bridge id)kSecAttrService : @"noraml1", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
只須要添加一個kSecAttrAccessGroup屬性便可。
(1)未對應用APP的entitlement(受權)進行配置時,APP使用鑰匙串存儲時,會默認存儲在自身BundleID的條目下。
(2)對APP的entitlement(受權)進行配置後,說明APP有了對某個條目的訪問權限。
鑰匙串的可視化效果可參見Mac的APP-鑰匙串訪問。
APP鑰匙串訪問權限的配置方法:(這裏XXXXX模擬器隨意,但真機必須爲本身開發者帳號ID,不然沒法經過編譯)
1.新建一個Plist文件,在Plist中的數組中添加能夠訪問的條目的名字(如KeychainAccessGroups.plist),結構以下:
Plist代碼:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>keychain-access-groups</key> <array> <string>XXXXX.GrassInfoAppFamily</string> </array> </dict> </plist>
2.在Build-setting中進行配置,搜索entitlement,注意路徑別配置錯:
這個屬性,決定了咱們item在什麼條件下能夠獲取到裏面的內容,咱們在添加item的時候,能夠添加這個屬性,來加強數據的安全性,具體的主要有如下幾個:
kSecAttrAccessibleWhenUnlocked
kSecAttrAccessibleAfterFirstUnlock
kSecAttrAccessibleAlways
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly
每一個意思都很明確,item默認就是kSecAttrAccessibleWhenUnlocked。也就是在設備未鎖屏的狀況下。這個也是蘋果推薦的。kSecAttrAccessibleAlways,這個蘋果在WWDC中也說了,不建議使用,蘋果本身已經都棄用了。kSecAttrAccessibleAfterFirstUnlock這個是在設備第一次解鎖後,可使用。這個最多見的就是後臺喚醒功能裏面,若是須要訪問某個item,那麼須要使用這個屬性,否則是訪問不了item的數據的。最後幾個DeviceOnly相關的設置,若是設置了,那麼在手機備份恢復到其餘設備時,是不能被恢復的。一樣iCloud也不會同步到其餘設備,由於在其餘設備上是解密不出來的。
keychain item能夠備份到iCloud上,咱們只須要在添加item的時候添加@{(__bridge id)kSecAttrSynchronizable : @YES,}。若是想同步到其餘設備上也能使用,請避免使用DeviceOnly設置或者其餘和設備相關的控制權限。
ACL是iOS8新增的API,iOS9以後對控制權限進行了細化。在原來的基礎上加了一層本地驗證,主要是配合TouchID一塊兒使用。對於咱們使用者來講,在以前的item操做是同樣的,只是在添加的時候,加了一個SecAccessControlRef對象。
CFErrorRef error = NULL; SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlUserPresence, &error); if (error) { NSLog(@"failed to create accessControl"); return; } NSDictionary *query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"accesscontrol test" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"accesscontrol", (__bridge id)kSecAttrAccessControl : (__bridge id)accessControl, }; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
咱們只須要建立SecAccessControlRef對象,主要是兩個參數,一個是kSecAttrAccessible,另外一個是SecAccessControlCreateFlags。在字典裏面添加(__bridge id)kSecAttrAccessControl : (__bridge id)accessControl便可。
SecAccessControlCreateFlags:
kSecAccessControlUserPresence
item經過鎖屏密碼或者Touch ID進行驗證,Touch ID能夠不設置,增長或者移除手指都能使用item。
kSecAccessControlTouchIDAny
item只能經過Touch ID驗證,Touch ID 必須設置,增長或移除手指都能使用item。
kSecAccessControlTouchIDCurrentSet
item只能經過Touch ID進行驗證,增長或者移除手指,item將被刪除。
kSecAccessControlDevicePasscode
item經過鎖屏密碼驗證訪問。
kSecAccessControlOr
若是設置多個flag,只要有一個知足就能夠。
kSecAccessControlAnd
若是設置多個flag,必須全部的都知足才行。
kSecAccessControlPrivateKeyUsage
私鑰簽名操做
kSecAccessControlApplicationPassword
額外的item密碼,可讓用戶本身設置一個訪問密碼,這樣只有知道密碼才能訪問。
獲取操做和之前的都是同樣的,只是加了一個提示信息kSecUseOperationPrompt,用來講明調用意圖:
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecAttrService : @"accesscontrol", (__bridge id)kSecUseOperationPrompt : @"獲取存儲密碼", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSString *pwd = [[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(dataTypeRef) encoding:NSUTF8StringEncoding]; NSLog(@"==result:%@", pwd); }
Secure Enclave 首次出如今iPhone 5s中,就是協處理器M7,用來保護指紋數據。SE裏面的數據咱們用戶層面代碼是訪問不了的,哪怕系統越獄了,也沒法訪問到裏面數據。只有特定的代碼才能去訪問(CPU 切換成Monitor Mode)。SE自己也集成了加密庫,加密解密相關的都在SE內部完成,這樣應用程序只能拿到最後的結果,而沒法拿到原始的數據。(關於Secure Enclave 能夠搜些資料瞭解下,這裏就不展開了)。在iOS9以後蘋果開放了一個新的屬性:kSecAttrTokenIDSecureEnclave,也就是將數據保存到SE裏面,固然只是key。
如何使用:
//生成ECC公私鑰 CFErrorRef error = NULL; SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlTouchIDAny, &error); if (error) { NSLog(@"failed to create accessControl"); return; } NSDictionary *params = @{ (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave, (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC, (__bridge id)kSecAttrKeySizeInBits: @256, (__bridge id)kSecPrivateKeyAttrs: @{ (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)accessControl, (__bridge id)kSecAttrIsPermanent: @YES, (__bridge id)kSecAttrLabel: @"ECCKey", }, }; SecKeyRef publickKey, privateKey; OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)params, &publickKey, &privateKey); [self handleError:status]; if (status == errSecSuccess) { CFRelease(privateKey); CFRelease(publickKey); } //簽名 NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate, (__bridge id)kSecAttrLabel: @"ECCKey", (__bridge id)kSecReturnRef: @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecUseOperationPrompt: @"簽名數據" }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Retrieve the key from the keychain. No authentication is needed at this point. SecKeyRef privateKey; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey); if (status == errSecSuccess) { // Sign the data in the digest/digestLength memory block. uint8_t signature[128]; size_t signatureLength = sizeof(signature); uint8_t digestData[16]; size_t digestLength = sizeof(digestData); status = SecKeyRawSign(privateKey, kSecPaddingPKCS1, digestData, digestLength, signature, &signatureLength); if (status == errSecSuccess) { NSLog(@"sign success"); } CFRelease(privateKey); } else { } });
以上代碼就是生成了一對公私鑰(ECC 256),私鑰會保存在SE中,而公鑰交給應用程序。簽名操做的時候,好像咱們取到了私鑰,可是實際上咱們並不能拿到私鑰,只是私鑰在SE中的一個引用。加密的操做也是在SE中完成,最後返回給咱們簽名的數據。
蘋果在這邊舉了個簡單例子,如何利用Touch ID進行登陸。客戶端生成一對公私鑰,公鑰發給服務器,客戶端在經過Touch ID校驗後,加密一段內容(私鑰簽名操做),將內容和結果發送給服務器,服務器取出公鑰進行驗籤。若是一致,則經過驗證。
上面這個圖就是普通item的一個解密流程。應用程序經過API訪問item,在keychain裏面取出加密的item,將加密的item,傳遞給SE解密,解密完返回給keychain,最後返回給應用。
iOS8後,蘋果將中間的keychain框架進行了拆分,增長了本地受權認證:
這個最大的用途就是和Touch ID進行結合,來提升咱們的數據安全性。當咱們取item的時候,若是須要Touch ID進行驗證,在SE裏面,若是經過驗證那麼將對數據進行解密,並返回給keychain,最後返回給應用程序。
iOS9以後的keyStore也放進了SE裏面,進一步提升了安全性。至於keychain的安全性在非越獄下的確是安全的,可是一旦手機越獄,應用能夠訪問到其餘應用程序item,或者經過Keychain-Dumper導出keychain數據,那麼就不是很安全了。因此在咱們存進鑰匙串的數據,不要直接存一些敏感信息,在程序中加一層數據保護。
參考:
安全白皮書
Keychain and Authentication with Touch ID
Protecting Secrets with the Keychain
Security and Your Apps
我在官方文檔中並未找到相關的更新:https://developer.apple.com/documentation/security/keychain_services
你們仍是能夠放心用的
iOS 10.3 還未正式發佈,beta 版中一個關於keychain 特性的小修改,就已經引發了普遍的關注。
改動以下:
若是 App 被刪除,以前存儲於 keychain 中的數據也會一同被清除。
若是使用了 keychain group,只要當 group 全部相關的 App 被刪除時,keychain 中的數據纔會被刪除。
這一改動,雖未經官方公佈。但已在論壇帖子裏獲得了 Apple 員工的確認,原文以下:
This is an intentional change in iOS 10.3 to protect user privacy. Information that can identify a user should not be left on the device after the app that created it has been removed.
It has never been a part of the API contract that keychain items created by an app would survive when the app is removed. This has always been an implementation detail.
If a keychain item is shared with other apps, it won’t be deleted until those other apps have been deleted as well.
若是這是這樣的話,那麼keychain存在還有什麼意義麼?
還有蘋果如今愈來愈注重用戶的隱私,就前幾天對於使用JSPatch熱更新的機制的應用發送的郵件來看,蘋果彷佛要在這方面有動做了,我想說,蘋果爸爸此次難道真的要爲Swift和OC兩個親兒子出頭了嗎?
其實我也以爲 app 都刪了 keychain 還在是挺不合理的一件事兒。在隱私保護上仍是能夠看得出 Apple 仍是一直在做爲。
因爲蘋果頻繁的更新,以前的一些東西已經不能使用https://forums.developer.apple.com/message/210531#210531