在Windows 10及.NET 8中,如何用RSACryptoServiceProvider公钥SignHash并私钥验签?
.NET 8中RSACryptoServiceProvider公钥签名与私钥验证问题
问题背景
此前提问过如何在.NET 8中使用RSACryptoServiceProvider执行SignHash操作,按建议加载私钥时设置X509KeyStorageFlags.Exportable后可正常签名,但对接的API要求仅用公钥执行SignHash,导致签名无法被验证。尝试用公钥执行SignHash时触发以下错误:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
执行请求时发生未处理的异常。
System.Security.Cryptography.CryptographicException:对象仅包含密钥对的公钥部分,必须同时提供私钥。
现需在Windows 10 + .NET 8环境下,实现用RSACryptoServiceProvider完成「公钥加密/验证、私钥签名/解密」的合规流程,相关原始代码如下:
/** RSACrypto Variables */ RSACryptoServiceProvider? key = null; RSACryptoServiceProvider? privateKey = null; string Payload = "Hello Sending sensitive data"; /** Load CIGServerCerts */ X509Certificate2Collection CIGServerCerts = new X509Certificate2Collection(); CIGServerCerts.Import(_appSettingsHelper.CIGServerCertCertificatePath, null, X509KeyStorageFlags.PersistKeySet); if (CIGServerCerts.Count > 0) { CIGServerCert = CIGServerCerts[0]; Console.WriteLine("Certificates found in the specified file."); } ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(Helper.CustomCertificateValidation); if (CIGServerCert != null && CIGServerCert.PublicKey != null) { RSA rsaPublicKey = this.CIGServerCert.GetRSAPublicKey(); key = new RSACryptoServiceProvider(); key.ImportParameters(rsaPublicKey.ExportParameters(false)); if (key != null) { Console.WriteLine("Key retrieved successfully"); } } /** Load Certificate for Stakeholder */ X509Certificate2Collection stakeHolderCerts = new X509Certificate2Collection(); if (stakeHolderCerts != null) { stakeHolderCerts.Import(_appSettingsHelper.StakeholderCertificatePath, _appSettingsHelper.StakeholderPassword, X509KeyStorageFlags.PersistKeySet); Console.WriteLine($"@A There is stakeholder certificate"); } if (stakeHolderCerts?.Count > 0) { this.StakeHolderCert = stakeHolderCerts[0]; Console.WriteLine("certificates found in the specified file."); } RSA rsaPrivateKey = this.StakeHolderCert.GetRSAPrivateKey(); RSAParameters rsaParams = rsaPrivateKey.ExportParameters(false); privateKey = new RSACryptoServiceProvider(); privateKey.ImportParameters(rsaParams); /** Serialize Data */ if (Payload.Trim().Length == 20) { Helper.CryptoOutput cryptoOutput = Payload.ToString().EncryptPayload(); // (This is a function that return Key, IV and Payload) works fine byte[] inArray = key.Encrypt(cryptoOutput.Key, false); string base64String1 = Convert.ToBase64String(key.Encrypt(cryptoOutput.IV, false)); string base64String2 = Convert.ToBase64String(inArray); string payload = cryptoOutput.Payload; SHA1Managed shA1Managed = new SHA1Managed(); byte[] hash = shA1Managed.ComputeHash(Convert.FromBase64String(cryptoOutput.Payload)); string oid = CryptoConfig.MapNameToOID("SHA1"); string base64String3 = Convert.ToBase64String(privateKey.SignHash(hash, oid)); string responseXModal = SerializationHelper.CreateDataRequest( base64String1, base64String2, payload, base64String3, "123.12.13.1", ApiConstant.ID, ApiConstant.TIMESTAMP, ApiConstant.USER_ID ); byte[] rgbSignature = Convert.FromBase64String(responseXModal.Signature); byte[] rgbHash = shA1Managed.ComputeHash(Convert.FromBase64String(responseXModal.Payload)); var signatureVerification = privateKey.VerifyHash(rgbHash, CryptoConfig.MapNameToOID("SHA1"), rgbSignature); Console.WriteLine($"KEY RESPONSE found in the specified file. {signatureVerification}"); if (signatureVerification) { /** Decrypt Keys */ byte[] i_IV = privateKey.Decrypt(Convert.FromBase64String(responseXModal.EncryptedCryptoIV), false); byte[] i_Key = privateKey.Decrypt(Convert.FromBase64String(responseXModal.EncryptedCryptoIV), false); } }
解决方案
核心逻辑纠正
RSA非对称加密的标准流程是:私钥签名、公钥验证;公钥加密、私钥解密。对接API要求「公钥签名」属于表述错误,实际正确流程应为:
- 己方用私钥签名请求,对方用己方公钥验证
- 对方用私钥签名响应,己方用对方公钥验证
修正后代码实现
/** RSACrypto Variables */ RSACryptoServiceProvider? serverPublicKey = null; // 对方服务器公钥:加密请求密钥、验证响应签名 RSACryptoServiceProvider? stakeholderPrivateKey = null; // 己方私钥:签名请求、解密响应密钥 string Payload = "Hello Sending sensitive data"; /** Load CIGServerCerts (对方服务器公钥证书) */ X509Certificate2Collection CIGServerCerts = new X509Certificate2Collection(); CIGServerCerts.Import(_appSettingsHelper.CIGServerCertCertificatePath, null, X509KeyStorageFlags.PersistKeySet); if (CIGServerCerts.Count > 0) { CIGServerCert = CIGServerCerts[0]; Console.WriteLine("服务器证书加载成功"); } ServicePointManager.ServerCertificateValidationCallback += Helper.CustomCertificateValidation; if (CIGServerCert != null) { using RSA rsaPublicKey = CIGServerCert.GetRSAPublicKey(); serverPublicKey = new RSACryptoServiceProvider(); serverPublicKey.ImportParameters(rsaPublicKey.ExportParameters(false)); Console.WriteLine("服务器公钥提取成功"); } /** Load Stakeholder Certificate (己方私钥证书) */ X509Certificate2Collection stakeHolderCerts = new X509Certificate2Collection(); stakeHolderCerts.Import(_appSettingsHelper.StakeholderCertificatePath, _appSettingsHelper.StakeholderPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); // 必须添加Exportable确保私钥可导出 if (stakeHolderCerts.Count > 0) { StakeHolderCert = stakeHolderCerts[0]; Console.WriteLine("己方证书加载成功"); } using RSA rsaPrivateKey = StakeHolderCert.GetRSAPrivateKey(); RSAParameters rsaParams = rsaPrivateKey.ExportParameters(true); // 传入true导出完整私钥参数 stakeholderPrivateKey = new RSACryptoServiceProvider(); stakeholderPrivateKey.ImportParameters(rsaParams); /** 数据处理与请求构建 */ if (Payload.Trim().Length == 20) { Helper.CryptoOutput cryptoOutput = Payload.EncryptPayload(); // 自定义对称加密逻辑 // 用对方公钥加密对称密钥和IV byte[] encryptedKey = serverPublicKey.Encrypt(cryptoOutput.Key, false); byte[] encryptedIV = serverPublicKey.Encrypt(cryptoOutput.IV, false); string base64EncryptedIV = Convert.ToBase64String(encryptedIV); string base64EncryptedKey = Convert.ToBase64String(encryptedKey); string encryptedPayload = cryptoOutput.Payload; // 用己方私钥对Payload哈希签名 using SHA1Managed sha1 = new SHA1Managed(); byte[] payloadHash = sha1.ComputeHash(Convert.FromBase64String(encryptedPayload)); string sha1Oid = CryptoConfig.MapNameToOID("SHA1"); byte[] signature = stakeholderPrivateKey.SignHash(payloadHash, sha1Oid); string base64Signature = Convert.ToBase64String(signature); // 构建请求 var requestModal = SerializationHelper.CreateDataRequest( base64EncryptedIV, base64EncryptedKey, encryptedPayload, base64Signature, "123.12.13.1", ApiConstant.ID, ApiConstant.TIMESTAMP, ApiConstant.USER_ID ); // 发送请求并获取响应(此处省略请求发送逻辑) // var responseXModal = SendRequestToApi(requestModal); // 验证响应签名:用对方公钥验证 if (responseXModal != null) { byte[] responseSignature = Convert.FromBase64String(responseXModal.Signature); byte[] responsePayloadHash = sha1.ComputeHash(Convert.FromBase64String(responseXModal.Payload)); bool isSignatureValid = serverPublicKey.VerifyHash(responsePayloadHash, sha1Oid, responseSignature); Console.WriteLine($"响应签名验证结果:{isSignatureValid}"); if (isSignatureValid) { // 用己方私钥解密响应中的对称密钥和IV byte[] decryptedIV = stakeholderPrivateKey.Decrypt(Convert.FromBase64String(responseXModal.EncryptedCryptoIV), false); byte[] decryptedKey = stakeholderPrivateKey.Decrypt(Convert.FromBase64String(responseXModal.EncryptedCryptoKey), false); } } }
关键修正点
- 密钥用途明确区分:对方公钥负责加密请求密钥、验证响应签名;己方私钥负责签名请求、解密响应密钥,完全符合RSA标准逻辑
- 私钥导出参数修正:导出己方私钥时传入
true,才能获取完整的私钥参数 - 验证逻辑修正:用对方公钥验证响应签名,原代码误用己方私钥验证,逻辑完全错误
- 证书加载标记补充:己方私钥证书加载时必须添加
X509KeyStorageFlags.Exportable,否则无法导出私钥参数
内容的提问来源于stack exchange,提问作者Andongwisye Mwamengo
相关产品推荐
相关产品推荐

