You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    
}

问题排查

  1. 私钥格式不兼容:SecKeyCreateWithData要求私钥为PKCS#8格式,但你的私钥是PKCS#1格式(PEM头为-----BEGIN RSA PRIVATE KEY-----),直接使用会导致私钥创建失败或无法与证书关联。
  2. Identity查询逻辑错误:kSecMatchItemList不能直接传入证书和私钥对象,且通过标签关联的方式无法保证两者的绑定关系,查询逻辑不可靠。
  3. 钥匙串操作冗余:无需先将证书和私钥存入钥匙串再查询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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 00:27:08