在IOS9中使用KeychainTouchID
来源:互联网 发布:易语言sql命令手册 编辑:程序博客网 时间:2024/06/15 15:43
前言
在IOS9之前如果我们想做指纹认证的功能,只能完全信任客户端的结果,如果客户端被破解那么指纹验证就可能被绕过。但是这一切在IOS9之后有了改观,后台服务器也能参与认证的过程了,接下来我们会详细介绍。
关键点
在IOS9之后苹果对keychain进行了改进,支持密钥的产生和使用在Secure Enclave中进行。具体可以参考wwdc2015 Security and Privacy
SecGenerateKeyPair
: 可以产生ECC P256的非对称密钥对,公钥会返回给程序私钥则直接送到Secure Enclave中,任何用户都无法获取私钥,只能通过 SecKeyRawSign 方法来请求签名,同时我们可以使用 SecAccessControlCreateWithFlags 来设置如果要使用私钥必须验证Touch ID。SecKeyRawSign
: 使用ECDSA数字签名算法来签名数据(注意这里是数字签名算法,不只是ECC加密),需要注意的是签名的数据长度是有限制的,我测试最多只能签名32个字节长度的数据。如何使用 SecGenerateKeyPair 和 SecKeyRawSign 请参考TouchIDKeyChainDemo
服务器参与指纹认证
客户端调用SecGenerateKeyPair产生密钥对。
将公钥上送到服务器进行存储。
服务器发送报文到客户端请求签名。
客户端使用SecKeyRawSign进行签名,在签名的时候系统会自动调用Touch ID验证用户指纹。
客户端将签名结果上送到服务器。
服务器使用公钥来验证签名。
使用 SecKeyRawSign 签名,用openssl验签的demo
#define Secp256r1CurveLen 256unsigned char Secp256r1header[] ={ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00};#define Secp256r1headerLen 26#define PublicKeyInitialTag @"-----BEGIN PUBLIC KEY-----\n"#define PublicKeyFinalTag @"\n-----END PUBLIC KEY-----"//产生密钥- (void)generateKeyAsync { CFErrorRef error = NULL; SecAccessControlRef sacObject; //设置ACL,使用kSecAccessControlTouchIDAny表示使用Touch ID来保护密钥。 sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDAny | kSecAccessControlPrivateKeyUsage, &error); NSDictionary *parameters = @{ (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,//表示使用SecureEnclave来保存密钥 (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC,//表示产生ECC密钥对,注意目前只支持256位的ECC算法 (__bridge id)kSecAttrKeySizeInBits: @256, (__bridge id)kSecPrivateKeyAttrs: @{ (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject, (__bridge id)kSecAttrIsPermanent: @YES, (__bridge id)kSecAttrLabel: @"my-se-key", }, }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ SecKeyRef publicKey, privateKey; OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)parameters, &publicKey, &privateKey); if (status == errSecSuccess) { NSLog(@"产生密码成功"); //这里先把公钥保存到keychain才能拿到真正的公钥数据 NSDictionary *pubDict = @{ (__bridge id)kSecClass : (__bridge id)kSecClassKey, (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC, (__bridge id)kSecAttrLabel : @"", (__bridge id)kSecAttrIsPermanent : @(YES), (__bridge id)kSecValueRef : (__bridge id)publicKey, (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPublic, (__bridge id)kSecReturnData : @(YES) }; CFTypeRef dataRef = NULL; status = SecItemAdd((__bridge CFDictionaryRef)pubDict, &dataRef); if(status == errSecSuccess){ NSLog(@"导出公钥成功"); //下面是将公钥转换为PEM格式,为了后面使用openssl验证签名 //PEM格式 = PublicKeyInitialTag + Base64(Secp256r1header + publicKeyData) + PublicKeyFinalTag NSData *publicKeyData = (__bridge NSData *)dataRef; NSLog(@"publicKeyData :%@",publicKeyData); NSMutableData *data = [NSMutableData dataWithBytes:Secp256r1header length:sizeof(Secp256r1header)]; [data appendData:publicKeyData]; NSString *base64String = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; NSMutableString *publicKeyStr = [NSMutableString string]; [publicKeyStr appendString:PublicKeyInitialTag]; [publicKeyStr appendString:base64String]; [publicKeyStr appendString:PublicKeyFinalTag]; self.pubKey = publicKeyStr; NSLog(@"%@",self.pubKey); }else{ NSLog(@"导出公钥失败"); } CFRelease(dataRef); CFRelease(privateKey); CFRelease(publicKey); }else{ NSLog(@"产生密码失败"); } });}//使用密钥- (void)useKeyAsync { NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate, (__bridge id)kSecAttrLabel: @"my-se-key", (__bridge id)kSecReturnRef: @YES, (__bridge id)kSecUseOperationPrompt: @"验证签名" }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ SecKeyRef privateKey; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey); if (status == errSecSuccess) { uint8_t sign[128]; size_t signatureLength = sizeof(sign); uint8_t dataToSign[12]; uint8_t hash[CC_SHA256_DIGEST_LENGTH]; CC_SHA256(dataToSign, 12, hash); //调用SecKeyRawSign的时候系统会自动调起Touch ID验证用户指纹 //指纹的验证和数据的签名都在Secure Enclave中进行保证了安全 status = SecKeyRawSign(privateKey, kSecPaddingPKCS1, dataToSign, sizeof(dataToSign), sign, &signatureLength); if (status == errSecSuccess) { NSLog(@"SecKeyRawSign签名数据成功"); //使用openSSL验证签名 const char *pemPubKey = [self.pubKey UTF8String]; BIO *buf = BIO_new_mem_buf((void*)pemPubKey, (int)self.pubKey.length); EC_KEY *ecKey = PEM_read_bio_EC_PUBKEY(buf, NULL, NULL, NULL); EC_KEY_print_fp(stdout, ecKey, 2); int ret = ECDSA_verify(0, dataToSign, sizeof(dataToSign),sign, (int)signatureLength, ecKey); if (ret == 1) { NSLog(@"openssl 验证签名成功"); }else{ NSLog(@"openssl 验证签名失败"); } } CFRelease(privateKey); } else { NSLog(@"SecKeyRawSign签名数据失败"); } });}//删除密钥- (void)deleteKeyAsync { NSDictionary *query = @{ (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave, (__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate, (__bridge id)kSecAttrLabel: @"my-se-key", (__bridge id)kSecReturnRef: @YES, }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); if(status == errSecSuccess){ NSLog(@"delete success"); }else{ NSLog(@"delete fail"); } });}
备注
注意在产生新的公私钥对前需要调用 deleteKeyAsync ,如果私钥已经存在调用 SecKeyGeneratePair 是会产生新的公私钥对,但是keychian中已经存在的私钥是不会被替换的,大家可以自己测试。
openssl的ios库可以使用OpenSSL-for-iPhone,一个命令就可以自动打包,非常方便。
- 在IOS9中使用KeychainTouchID
- ios9中 UIStackView的使用
- iOS9 中使用HTTP协议
- iOS9中UIStackView的使用
- iOS9 中UIStackView的使用
- 在ios9上使用HTTP请求
- 在IOS8及IOS9中使用UILocationNotification本地推送时不显示推送
- iOS9中URL Schemes的使用
- iOS9中spotlight的简单使用
- iOS9 在 iOS9 中现在找不到 XXX.dylib 了,比如libz.tbd
- iOS9 ATS特性以及在Xcode7中针对iOS9开启不安全的http连接
- IOS9:如何在 iOS 9 中创建 Stack View
- CFBundleDisplayName 在IOS9中未设置会造成的问题
- 在iOS9中突破微信App跳转的限制
- 解决Firemonkey TWebBrowser在IOS9中不工作的问题
- #在ios9及以上系统中创建对话框并弹出
- iOS -iOS9中提示框(UIAlertController)的常见使用
- iOS9与XCode7中不能使用http连接的解决办法
- The dependency `****` is not used in any concrete target问题解决方案
- Java常见面试题总结
- tomcat小知识
- Laravel 自定义错误页面
- linux的early printk的探讨
- 在IOS9中使用KeychainTouchID
- 第一天
- reversi-cli - 跟风做的一个黑白棋小游戏
- Debug
- Learning Python 002 print() 和 input()
- jquery发送Ajax(POST方法)
- 打开.db的数据库时报错,显示不能在一个关闭的数据集上执行此操作
- 快速排序算法
- 如何使用gdbclient gdbserver调试C/C++ native文件