keychain(二)

作者: JamesYu | 来源:发表于2016-06-14 01:04 被阅读2150次

    上一篇主要讲了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);
    
    

    我们只需要创建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中,就是协处理器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解密过程

    上面这个图就是普通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

    相关文章

      网友评论

      本文标题:keychain(二)

      本文链接:https://www.haomeiwen.com/subject/nptudttx.html