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相同,只是多了一步转换操作。
额外注意点
- 哈希算法一致性:确保加密狗签名使用的哈希算法(如SHA256)和Swift验证签名时指定的算法(如
kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256)完全匹配。 - 签名编码格式:加密狗返回的签名通常是PKCS#1 v1.5格式的原始签名,验证时需对应使用兼容的算法。
- CSR公钥正确性:生成CSR时,确保嵌入的公钥是从
SecKey导出的正确外部表示,而非手动修改后的错误数据。
内容的提问来源于stack exchange,提问作者ahad alam

