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);
我们只需要创建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