keychain(二)

上一篇主要講了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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容