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

CSR签名无效排查:PKCS#8与PKCS#1密钥格式冲突问题

问题描述

我用Safenet SDK在加密狗(Dongle)中生成了公钥和私钥,发现二者均为PKCS#8格式,分别以BEGIN PUBLIC KEY和BEGIN PRIVATE KEY开头,而非RSA密钥常用的BEGIN RSA PUBLIC KEY和BEGIN RSA PRIVATE KEY。

我已对CertificationInfo(如通用名、邮政编码等专有名称)完成签名,随后将公钥和签名后的数据传入Swift应用生成CSR。在CSR生成步骤中,我通过移除公钥前32个字符将其转换为RSA格式,再转为SecKey用于生成CSR,且发现SecKey转换前后的二进制数据一致。

但使用公钥验证签名时,系统提示“Signature is invalid”。想请教:

  • 这是否是因为签名用的私钥为PKCS#8格式,而验证用的公钥被转为PKCS#1格式导致的?
  • 若为此原因,可行的修复方案是什么?是否需要在C端转换密钥格式?如何在C中生成PKCS#1格式的密钥?或者,是否有办法在Swift中生成PKCS#8格式的SecKey?

我的CSR生成代码如下:

public func buildCSRAndReturnStringUsingDongle(enrollmentId: String, password: String) -> String? {
    let tagPublic = "public" + enrollmentId
    self.dongle = DongleManager.getInstance()
    self.dongle?.generateKeyPair(enrollmentId: enrollmentId, password: password)
    
    let publicKeyFromDongle: String = (self.dongle?.getPublicKeyBits(enrollmentId: enrollmentId, password: password))!

    print("Public Key is: \n",publicKeyFromDongle.dropFirst(32))

    let keyDict: [NSString: Any] = [
        kSecAttrKeyType: kSecAttrKeyTypeRSA,
        kSecAttrKeyClass: kSecAttrKeyClassPublic,
        kSecAttrApplicationTag: tagPublic.data(using: .utf8),
    ]

    var error: Unmanaged<CFError>?
    let publicKeyFromDongleEncodedData = publicKeyFromDongle.dropFirst(32).data(using: .utf8)!
    let publicKeyFromDongleData = Data(base64Encoded: publicKeyFromDongleEncodedData)
    
    guard let publicKeySecKey = SecKeyCreateWithData(publicKeyFromDongleData! as CFData, keyDict as CFDictionary, &error) else {
        print("Failed to create public key:", error!.takeRetainedValue())
        return nil
    }
    
    if let cfdata = SecKeyCopyExternalRepresentation(publicKeySecKey, &error) {
       let data:Data = cfdata as Data
       let b64Key = data.base64EncodedString()
       print("after : \n")
       print(b64Key)
    }
    
    let publickeyBits = KeyPairManager.getInstance().getPublicKeyBits(publicKey: publicKeySecKey, enrollmentId: enrollmentId).0!
    print("public key bits conversion: \n");
    print(publickeyBits)
    
    let publickeyBitss = publicKeyFromDongleData as! CFData as Data
    print("public key 64: \n")
    print(publickeyBitss)
    
    let certificationRequestInfo = buldCertificationRequestInfo(publickeyBits)
    let bytes: [UInt8] = certificationRequestInfo.map { $0 }
    let certificationRequestStr = String(decoding: bytes, as: UTF8.self)
    let signaturedStringData = self.dongle?.signn(password: password, data : certificationRequestInfo as CFData, enrollmentId: enrollmentId)
    print("signatured string with data: \n")
    print(signaturedStringData)
    
    var signature = [UInt8](repeating: 0, count: 256)
    var signatureLen: Int = signature.count
    let signatureData = signaturedStringData!.data(using: .hexadecimal)
    signatureData!.copyBytes(to: &signature, count: signatureData!.count)
    
    if let string = String(bytes: signature, encoding: .utf8) {
        print(string)
    } else {
        print("not a valid UTF-8 sequence")
    }
    
    signatureLen = signatureData!.count
    print("signature length: " + String(signatureLen))
    print("signature: "+signatureData!.base64EncodedString())

    var certificationRequest = Data(capacity: 1024)
    certificationRequest.append(certificationRequestInfo)

    let shaBytes = keyAlgorithm.sequenceObjectEncryptionType
    print("shaBytes are \n");
    print(shaBytes)
    certificationRequest.append(shaBytes, count: shaBytes.count)

    var signData = Data(capacity: 2049)
    let zero: UInt8 = 0 // Prepend zero
    signData.append(zero)
    signData.append(signature, count: signatureLen)
    appendBITSTRING(signData, into: &certificationRequest)

    enclose(&certificationRequest, by: sequenceTag) // Enclose into SEQUENCE
    
    let csrString = certificationRequest.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
        .addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
    print(csrString)

    let head = "-----BEGIN CERTIFICATE REQUEST-----\n"
    let foot = "-----END CERTIFICATE REQUEST-----\n"
    var isMultiple = false
    var newCSRString = head

    //Check if string size is a multiple of 64
    if csrString!.count % 64 == 0 {
        isMultiple = true
    }

    for (integer, character) in csrString!.enumerated() {
        newCSRString.append(character)

        if (integer != 0) && ((integer + 1) % 64 == 0) {
            newCSRString.append("\n")
        }

        if (integer == csrString!.count-1) && !isMultiple {
            newCSRString.append("\n")
        }

    }

    newCSRString += foot
    return newCSRString
}
解答

核心原因分析

签名验证失败和你手动修改公钥格式有直接关系。PKCS#8格式的公钥(BEGIN PUBLIC KEY)是DER编码的X.509 SubjectPublicKeyInfo结构,包含算法标识符和实际公钥内容;而PKCS#1格式(BEGIN RSA PUBLIC KEY)只是纯RSA公钥的DER编码。你直接移除前32个字符的暴力截断操作,破坏了原有的ASN.1结构,导致生成的SecKey和签名使用的私钥参数不匹配,最终验证失败。

另外要明确:签名验证的核心是密钥对的参数匹配,和密钥的封装格式无关——PKCS#8只是密钥的容器格式,只要公钥能正确解析出对应的RSA模、指数等参数,就能和对应的PKCS#8私钥完成签名验证。问题出在对PKCS#8公钥的错误处理,而非格式本身的差异。

修复方案

方案1:直接在Swift中解析PKCS#8格式的公钥(推荐)

完全不需要手动修改公钥内容,SecKeyCreateWithData可以直接解析PKCS#8格式的公钥,只需调整密钥属性字典:

let publicKeyFromDongle: String = // 从加密狗获取的完整PKCS#8公钥字符串(包含BEGIN/END头)
// 清理公钥字符串:移除头尾标识、换行和空白字符
let cleanedPublicKey = publicKeyFromDongle
    .replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
    .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
    .replacingOccurrences(of: "\n", with: "")
    .trimmingCharacters(in: .whitespacesAndNewlines)

// 解码Base64得到原始DER数据
guard let publicKeyData = Data(base64Encoded: cleanedPublicKey) else {
    print("公钥Base64解码失败")
    return nil
}

let keyDict: [NSString: Any] = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass: kSecAttrKeyClassPublic,
    kSecAttrKeySizeInBits: 2048, // 替换为你的密钥实际长度,比如2048/4096
    kSecAttrApplicationTag: tagPublic.data(using: .utf8)
]

var error: Unmanaged<CFError>?
guard let publicKeySecKey = SecKeyCreateWithData(publicKeyData as CFData, keyDict as CFDictionary, &error) else {
    print("公钥解析失败:", error!.takeRetainedValue())
    return nil
}

这样生成的SecKey是从完整的PKCS#8公钥解析而来,和加密狗中的私钥参数完全匹配,验证签名时即可正常通过。

方案2:在C端转换公钥为PKCS#1格式(若必须)

如果业务要求必须使用PKCS#1格式公钥,可借助OpenSSL库完成转换(建议先查阅Safenet SDK文档,部分SDK支持直接导出PKCS#1格式密钥):

#include <openssl/pem.h>
#include <openssl/x509.h>

// 示例:从PKCS#8 PEM公钥转换为PKCS#1格式
int convert_pkcs8_to_pkcs1(const char* pkcs8_pem_path, const char* pkcs1_pem_path) {
    FILE* pkcs8_fp = fopen(pkcs8_pem_path, "r");
    if (!pkcs8_fp) { return -1; }
    
    EVP_PKEY* pkey = PEM_read_PUBKEY(pkcs8_fp, NULL, NULL, NULL);
    fclose(pkcs8_fp);
    if (!pkey) { return -2; }
    
    RSA* rsa = EVP_PKEY_get1_RSA(pkey);
    if (!rsa) { 
        EVP_PKEY_free(pkey);
        return -3; 
    }
    
    FILE* pkcs1_fp = fopen(pkcs1_pem_path, "w");
    if (!pkcs1_fp) {
        RSA_free(rsa);
        EVP_PKEY_free(pkey);
        return -4;
    }
    
    PEM_write_RSAPublicKey(pkcs1_fp, rsa);
    fclose(pkcs1_fp);
    
    RSA_free(rsa);
    EVP_PKEY_free(pkey);
    return 0;
}

注意:这种转换仅改变密钥的封装格式,核心公钥参数和PKCS#8格式完全一致,本质效果和方案1相同,只是多了一步转换操作。

额外注意点

  1. 哈希算法一致性:确保加密狗签名使用的哈希算法(如SHA256)和Swift验证签名时指定的算法(如kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256)完全匹配。
  2. 签名编码格式:加密狗返回的签名通常是PKCS#1 v1.5格式的原始签名,验证时需对应使用兼容的算法。
  3. CSR公钥正确性:生成CSR时,确保嵌入的公钥是从SecKey导出的正确外部表示,而非手动修改后的错误数据。

内容的提问来源于stack exchange,提问作者ahad alam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:47:03