iOS 17中SecKeyCreateWithData始终返回nil问题求助
解决iOS 17中SecKeyCreateWithData返回nil的问题
问题根源
iOS 17对EC私钥的导入格式、属性配置的校验比iOS 16.4更严格,主要可能是以下原因:
- 重复设置
kSecAttrIsPermanent属性(顶层和kSecPrivateKeyAttrs中都配置)导致冲突 - 私钥数据格式不符合iOS 17要求(比如非PKCS#8格式、包含额外字节)
- 使用
SecKeyCreateWithData创建永久存储密钥的方式在iOS 17中存在兼容性问题
解决方案
方案1:修正属性配置,移除重复设置
先移除顶层的kSecAttrIsPermanent,仅在kSecPrivateKeyAttrs中配置:
var attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits as String: 256, kSecAttrAccessGroup as String: accessGroup, kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true, kSecAttrAccessControl as String: access ] ] var error: Unmanaged<CFError>? guard let secKey = SecKeyCreateWithData(privateKey as CFData, attributes as CFDictionary, &error) else { print(error.debugDescription) throw EosioError(.keyManagementError, reason: error.debugDescription) }
方案2:改用SecItemAdd直接导入密钥(推荐)
iOS 17中创建永久存储的密钥,更可靠的方式是通过SecItemAdd导入,再通过查询获取密钥引用:
// 1. 准备导入属性 let importAttributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits as String: 256, kSecAttrAccessGroup as String: accessGroup, kSecValueData as String: privateKey, kSecAttrIsPermanent as String: true, kSecAttrAccessControl as String: access ] // 2. 导入到钥匙串 let importStatus = SecItemAdd(importAttributes as CFDictionary, nil) guard importStatus == errSecSuccess else { let error = NSError(domain: NSOSStatusErrorDomain, code: Int(importStatus), userInfo: nil) throw EosioError(.keyManagementError, reason: error.debugDescription) } // 3. 查询获取SecKey引用 let query: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrAccessGroup as String: accessGroup, kSecReturnRef as String: true ] var secKey: SecKey? let queryStatus = SecItemCopyMatching(query as CFDictionary, &secKey) guard queryStatus == errSecSuccess, let key = secKey else { throw EosioError(.keyManagementError, reason: "Failed to retrieve private key from keychain") }
方案3:转换私钥为PKCS#8格式导入
如果私钥是原始32字节EC密钥(未经过PKCS#8包装),可以先转换为PKCS#8 DER格式再导入:
// 辅助方法:将原始32字节EC私钥转换为PKCS#8 DER格式 func convertRawECPrivateKeyToPKCS8(rawKey: Data) -> Data? { guard rawKey.count == 32 else { return nil } // ASN.1结构:PKCS#8 PrivateKeyInfo let version = Data([0x02, 0x01, 0x00]) // 版本0 // AlgorithmIdentifier: ecPublicKey + secp256r1曲线 let ecOID = Data([0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]) let secp256r1OID = Data([0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A]) let algorithmIdentifier = Data([0x30, 0x13]) + ecOID + Data([0x03, 0x0D, 0x00]) + secp256r1OID // 私钥内容(OCTET STRING包装) let privateKeyOctet = Data([0x04]) + rawKey // 组装完整序列 let totalLength = version.count + algorithmIdentifier.count + privateKeyOctet.count guard totalLength <= 255 else { return nil } // 这里仅适配256位密钥 let sequenceHeader = Data([0x30, UInt8(totalLength)]) return sequenceHeader + version + algorithmIdentifier + privateKeyOctet } // 使用示例 // 1. 提取原始32字节私钥(需根据你的EOS私钥格式去除前缀、校验和等额外字节) guard let rawPrivateKey = extractRaw32ByteKey(from: privateKey) else { throw EosioError(.keyManagementError, reason: "Invalid private key format") } // 2. 转换为PKCS#8 guard let pkcs8Data = convertRawECPrivateKeyToPKCS8(rawKey: rawPrivateKey) else { throw EosioError(.keyManagementError, reason: "Failed to convert key to PKCS#8 format") } // 3. 导入(使用方案1或方案2的代码)
额外排查点
- 检查
accessControl配置:如果使用了生物识别相关的控制策略,确保设备支持且权限配置正确,可临时改用kSecAccessControlDevicePasscode测试是否是权限问题 - 验证私钥数据:确保解码后的私钥是32字节的纯EC密钥,无额外前缀、校验和字节(EOS私钥通常包含4字节校验和,需去除)
内容的提问来源于stack exchange,提问作者Artur Tkachenko
相关产品推荐
相关产品推荐

