Swift中使用PEM证书与私钥创建SecIdentity失败求助
问题:创建SecIdentity失败排查与修复
问题背景
持有PEM格式的X509证书和RSA私钥,通过以下JS代码转换为Base64格式:
export const convertPemToBase64 = (pemKey, type) => { const pemHeader = type === 'certificate' ? '-----BEGIN CERTIFICATE-----' : '-----BEGIN RSA PRIVATE KEY-----'; const pemFooter = type === 'certificate' ? '-----END CERTIFICATE-----' : '-----END RSA PRIVATE KEY-----'; let base64Key = pemKey.replace(pemHeader, '').replace(pemFooter, ''); // Remove any newline characters base64Key = base64Key.replace(/\r?\n|\r/g, ''); return base64Key; };
转换后得到CertificateBase64(MIIC3jCCAcYCAQAw...9gBFNQUdahSccXF2bnZkv2Kh)和PrivateBase64(MIIEowIBAAKCAQE...ooxp1Nyl17zfP)。在Swift中调用自定义loadIdentity函数创建HTTPS请求所需的SecIdentity时,报错Unable to create identity,原函数代码如下:
func loadIdentity(certificate: String, privateKey: String) -> SecIdentity? { print("privateKey: \(privateKey)") guard let certData = Data(base64Encoded: certificate) else { print("Unable to decode certificate PEM") return nil } print("certData: \(certData)") // Create certificate object guard let cert = SecCertificateCreateWithData(nil, certData as CFData) else { print("Unable to create certificate") return nil } // Add certificate to the keychain let certAddQuery: [NSString: Any] = [ kSecClass: kSecClassCertificate, kSecValueRef: cert, kSecAttrLabel: "myCertificate" ] var status = SecItemAdd(certAddQuery as CFDictionary, nil) if status != errSecSuccess && status != errSecDuplicateItem { print("Failed to add certificate to keychain: \(status)") return nil } guard let keyData = Data(base64Encoded: privateKey) else { print("Unable to decode private key PEM") return nil } print("keyData: \(keyData)") // Define attributes for the private key let keyDict: [NSString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits: 2048, kSecReturnPersistentRef: true ] // Create private key object var error: Unmanaged<CFError>? guard let privateKeyData = SecKeyCreateWithData(keyData as CFData, keyDict as CFDictionary, &error) else { // print("Unable to create private key: \(error?.takeRetainedValue() ?? "Unknown error" as CFError)") print("Unable to create private key") return nil } // Add private key to the keychain let keyAddQuery: [NSString: Any] = [ kSecClass: kSecClassKey, kSecValueRef: privateKeyData, kSecAttrLabel: "myPrivateKey", kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked ] status = SecItemAdd(keyAddQuery as CFDictionary, nil) if status != errSecSuccess && status != errSecDuplicateItem { print("Failed to add private key to keychain: \(status)") return nil } // Query to retrieve the identity from the keychain let identityQuery: [NSString: Any] = [ kSecClass: kSecClassIdentity, kSecReturnRef: true, kSecAttrLabel: "myCertificate", kSecMatchItemList: [cert, privateKeyData] ] var identity: CFTypeRef? status = SecItemCopyMatching(identityQuery as CFDictionary, &identity) guard status == errSecSuccess else { print("Unable to create identity") return nil } return (identity as! SecIdentity) }
问题排查
- 私钥格式不兼容:
SecKeyCreateWithData要求私钥为PKCS#8格式,但你的私钥是PKCS#1格式(PEM头为-----BEGIN RSA PRIVATE KEY-----),直接使用会导致私钥创建失败或无法与证书关联。 - Identity查询逻辑错误:
kSecMatchItemList不能直接传入证书和私钥对象,且通过标签关联的方式无法保证两者的绑定关系,查询逻辑不可靠。 - 钥匙串操作冗余:无需先将证书和私钥存入钥匙串再查询Identity,直接在内存中创建关联更高效可靠。
修复方案
核心修正点
- 将PKCS#1格式的私钥转换为PKCS#8格式,适配
SecKeyCreateWithData的要求。 - 使用
SecIdentityCreateWithCertificate+SecIdentitySetPrivateKey直接在内存中创建关联的Identity,避免钥匙串查询的不确定性。
修复后的完整代码
func loadIdentity(certificate: String, privateKey: String) -> SecIdentity? { // 解析证书 guard let certData = Data(base64Encoded: certificate) else { print("Unable to decode certificate Base64") return nil } guard let cert = SecCertificateCreateWithData(nil, certData as CFData) else { print("Unable to create certificate") return nil } // 解析PKCS#1格式私钥并转换为PKCS#8 guard let pk1Data = Data(base64Encoded: privateKey) else { print("Unable to decode private key Base64") return nil } // 构建PKCS#8格式的ASN.1结构 let rsaOID = Data([0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]) // RSA算法OID let version = Data([0x02, 0x01, 0x00]) // 版本号0 let algorithmIdentifier = Data([0x30, 0x0D]) + rsaOID + Data([0x05, 0x00]) // 算法标识 let privateKeyOctetString = Data([0x04, 0x82]) + pk1Data.count.toTwoByteBigEndian() + pk1Data // 计算总长度并拼接完整PKCS#8数据 let totalContentLength = version.count + algorithmIdentifier.count + privateKeyOctetString.count let pk8Data = Data([0x30, 0x82]) + totalContentLength.toTwoByteBigEndian() + version + algorithmIdentifier + privateKeyOctetString // 创建私钥 let keyAttributes: [CFString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits: 2048 ] var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateWithData(pk8Data as CFData, keyAttributes as CFDictionary, &error) else { print("Unable to create private key: \(error?.takeRetainedValue().localizedDescription ?? "Unknown error")") return nil } // 创建SecIdentity并关联私钥 var identity: SecIdentity? let createStatus = SecIdentityCreateWithCertificate(nil, cert, &identity) guard createStatus == errSecSuccess, let createdIdentity = identity else { print("Unable to create identity: \(createStatus)") return nil } let associateStatus = SecIdentitySetPrivateKey(createdIdentity, privateKey) if associateStatus != errSecSuccess { print("Failed to associate private key with identity: \(associateStatus)") return nil } // 可选:将Identity存入钥匙串(按需使用) let addQuery: [CFString: Any] = [ kSecClass: kSecClassIdentity, kSecValueRef: createdIdentity, kSecAttrLabel: "myIdentity", kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked ] let addStatus = SecItemAdd(addQuery as CFDictionary, nil) if addStatus != errSecSuccess && addStatus != errSecDuplicateItem { print("Failed to add identity to keychain: \(addStatus)") } return createdIdentity } // 辅助方法:将Int转换为两字节大端格式数据 extension Int { func toTwoByteBigEndian() -> Data { var value = self.bigEndian return Data(bytes: &value, count: 2) } }
额外说明
- 如果你的私钥本身是PKCS#8格式(PEM头为
-----BEGIN PRIVATE KEY-----),可以跳过PKCS#1转PKCS#8的步骤,直接使用原Base64数据创建私钥。 - 若不需要持久化Identity到钥匙串,可以删除最后存入钥匙串的代码块,减少不必要的IO操作。
内容的提问来源于stack exchange,提问作者Coder87
相关产品推荐
相关产品推荐

