如何使用生物识别/kSecAttrAccessControl保护SecIdentity?
根因说明
SecIdentity是钥匙串系统中私钥与对应X.509证书的逻辑关联对象,你遇到的失败主要是3个常见配置错误导致:
- 私钥访问控制参数配置缺失:配置生物识别保护时未添加
kSecAccessControlPrivateKeyUsage标识,导致URLAuthChallenge触发时,系统网络栈无法调用受保护的私钥完成签名验签流程 - 私钥与证书关联属性不匹配:钥匙串需要通过完全一致的
kSecAttrApplicationTag、kSecAttrLabel属性匹配私钥和证书,属性不一致时无法生成对应的SecIdentity对象 - 查询SecIdentity时缺少鉴权参数:查询受生物识别保护的私钥关联的Identity时,未配置允许触发生物识别验证的参数,会被钥匙串直接拦截返回查询失败
正确实现步骤
1. 生成带生物识别保护的RSA密钥对
创建访问控制对象时必须添加.privateKeyUsage标识,同时固定私钥的关联标签后续给证书复用:
// 创建带生物识别的访问控制配置 guard let accessControl = SecAccessControlCreateWithFlags( kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, [.userPresence, .privateKeyUsage], // 必须添加privateKeyUsage允许私钥用于签名操作 nil ) else { // 处理设备不支持生物识别/未设置密码的场景 return } // 密钥对生成参数 let keyGenParams: [CFString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits: 2048, kSecAttrAccessControl: accessControl, kSecAttrIsPermanent: true, kSecAttrApplicationTag: "com.your.bundle.id.rsa.tag".data(using: .utf8)! // 固定唯一标签,后续复用 ] var publicKey, privateKey: SecKey? let status = SecKeyGeneratePair(keyGenParams as CFDictionary, &publicKey, &privateKey) guard status == errSecSuccess, let publicKey = publicKey else { return } // 导出公钥发给服务器获取X.509证书
2. 导入服务器返回的X.509证书
导入证书时必须设置和私钥完全一致的关联标签,同时标记为身份证书:
let certData = // 服务器返回的X.509证书二进制数据 guard let cert = SecCertificateCreateWithData(nil, certData as CFData) else { return } // 可选:校验证书公钥与本地生成公钥是否一致,避免关联失败 guard let certPublicKey = SecCertificateCopyKey(cert), SecKeyIsEqualToKey(certPublicKey, publicKey) else { return } let certAddParams: [CFString: Any] = [ kSecClass: kSecClassCertificate, kSecValueRef: cert, kSecAttrApplicationTag: "com.your.bundle.id.rsa.tag".data(using: .utf8)!, // 和私钥标签完全一致 kSecAttrIsIdentity: kCFBooleanTrue!, kSecAttrAccessible: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly ] let addCertStatus = SecItemAdd(certAddParams as CFDictionary, nil) guard addCertStatus == errSecSuccess || addCertStatus == errSecDuplicateItem else { return }
3. 查询SecIdentity
查询时必须添加允许触发生物识别验证的参数:
let identityQuery: [CFString: Any] = [ kSecClass: kSecClassIdentity, kSecAttrApplicationTag: "com.your.bundle.id.rsa.tag".data(using: .utf8)!, kSecReturnRef: kCFBooleanTrue!, kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow // 允许触发生物识别弹窗 ] var identityRef: CFTypeRef? let queryStatus = SecItemCopyMatching(identityQuery as CFDictionary, &identityRef) guard queryStatus == errSecSuccess, let identity = identityRef as? SecIdentity else { // 处理查询失败,比如用户取消生物识别验证的场景 return } // 拿到的identity可直接用于URLAuthChallenge处理
原有方案的问题修正
writeIdentityWithBiometrics方案:按上述步骤补充.privateKeyUsage标识、保证私钥和证书的关联标签一致即可正常使用writePrivateKeyWithBiometrics方案:SecIdentity只是逻辑关联对象,本身无法直接配置访问控制,需要先查询到私钥的引用,再通过SecItemUpdate接口更新私钥的kSecAttrAccessControl属性,更新完成后再查询SecIdentity即可
注意:如果使用后台URLSession处理请求,需要提前用LAContext完成生物识别验证,将验证后的context传入查询参数的
kSecUseAuthenticationContext字段,避免后台场景无法弹出生物识别验证窗口导致失败。
内容的提问来源于stack exchange,提问作者swalkner
相关产品推荐
相关产品推荐

