上一篇主要講了keychain的基本使用,這篇主要講keychain安全方面的一些東西。
kSecAttrAccessible
這個屬性,決定了我們item在什么條件下可以獲取到里面的內容,我們在添加item的時候,可以添加這個屬性,來增強數據的安全性,具體的主要有以下幾個:
kSecAttrAccessibleWhenUnlocked
kSecAttrAccessibleAfterFirstUnlock
kSecAttrAccessibleAlways
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly
每個意思都很明確,item默認就是kSecAttrAccessibleWhenUnlocked。也就是在設備未鎖屏的情況下。這個也是蘋果推薦的。kSecAttrAccessibleAlways,這個蘋果在WWDC中也說了,不建議使用,蘋果自己已經都棄用了。kSecAttrAccessibleAfterFirstUnlock這個是在設備第一次解鎖后,可以使用。這個最常見的就是后臺喚醒功能里面,如果需要訪問某個item,那么需要使用這個屬性,不然是訪問不了item的數據的。最后幾個DeviceOnly相關的設置,如果設置了,那么在手機備份恢復到其他設備時,是不能被恢復的。同樣iCloud也不會同步到其他設備,因為在其他設備上是解密不出來的。
iCloud
keychain item可以備份到iCloud上,我們只需要在添加item的時候添加@{(__bridge id)kSecAttrSynchronizable : @YES,}。如果想同步到其他設備上也能使用,請避免使用DeviceOnly設置或者其他和設備相關的控制權限。
Access Control
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);
我們只需要創(chuàng)建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
Secure Enclave 首次出現在iPhone 5s中,就是協(xié)處理器M7,用來保護指紋數據。SE里面的數據我們用戶層面代碼是訪問不了的,哪怕系統(tǒng)越獄了,也無法訪問到里面數據。只有特定的代碼才能去訪問(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進行登錄??蛻舳松梢粚借€,公鑰發(fā)給服務器,客戶端在通過Touch ID校驗后,加密一段內容(私鑰簽名操作),將內容和結果發(fā)送給服務器,服務器取出公鑰進行驗簽。如果一致,則通過驗證。
item解密過程

上面這個圖就是普通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