I need to send data to a webservice everyday so I made a scheduled task that runs my code. The problem is the webservice requires a certificate with a PIN code. I have attached the certificate but I can't find a way to set the PIN to it, therefor it shows a popup everytime to enter it manually.
Here is my code for the certificate:
private void SendData(string data)
{
using (SerWSService webService = new SerWSService())
{
string certificateSN = "serial number for the certificate";
webService.ClientCertificates.Add(FindCertificate(certificateSN));
webService.SendData(data);
}
}
private X509Certificate2 FindCertificate(string certserial)
{
X509Certificate2 WPE_UserCert = null;
X509Store wstore = default(X509Store);
wstore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
wstore.Open(OpenFlags.ReadOnly);
var wcerts = wstore.Certificates;
foreach (var wcert in wcerts)
{
if (wcert.SerialNumber.ToUpper() == certserial.Replace(" ", "").ToUpper())
{
WPE_UserCert = wcert;
break;
}
}
wstore.Close();
if (WPE_UserCert != null)
{
//TO DO: add PIN code to certificate
}
return WPE_UserCert;
}
Is there any way I can set the PIN to the certificate?
No, because certificates don't have PINs; (private) keys do.
If you are finding a certificate with a private key and you pass that certificate to a class that expects the unified pair (e.g. SslStream, HttpClient) then there's no real/good solution.
If you are using the private key yourself, you have some leeway:
using (RSA rsa = cert.GetRSAPrivateKey())
{
RSACng rsaCng = rsa as RSACng;
RSACryptoServiceProvider rsaCsp = rsa as RSACryptoServiceProvider;
if (rsaCng != null)
{
// Set the PIN, an explicit null terminator is required to this Unicode/UCS-2 string.
byte[] propertyBytes;
if (pin[pin.Length - 1] == '\0')
{
propertyBytes = Encoding.Unicode.GetBytes(pin);
}
else
{
propertyBytes = new byte[Encoding.Unicode.GetByteCount(pin) + 2];
Encoding.Unicode.GetBytes(pin, 0, pin.Length, propertyBytes, 0);
}
const string NCRYPT_PIN_PROPERTY = "SmartCardPin";
CngProperty pinProperty = new CngProperty(
NCRYPT_PIN_PROPERTY,
propertyBytes,
CngPropertyOptions.None);
rsaCng.Key.SetProperty(pinProperty);
}
else if (rsaCsp != null)
{
// This is possible, but painful.
// Copy out the CspKeyContainerInfo data into a new CspParameters,
// build the KeyPassword value on CspParameters,
// Dispose() the existing instance,
// and open a new one to replace it.
//
// But if you really called GetRSAPrivateKey() and it has returned an RSACng for
// your device once, it pretty much will forever (until the next
// new RSA type for Windows is invented... at which point it still
// won't return an RSACryptoServiceProvider).
}
else
{
// No other built-in RSA types support setting a PIN programmatically.
}
// Use the key here.
// If you needed to return it, don't put it in a using :)
}
In the case of a CSP private key use CryptAcquireCertificatePrivateKey to acquire CryptoProv handle and then use CryptSetProvParam(h,PP_SIGNATURE_PIN,pin,0) to set the PIN.
Related
I'm writing a .NET 6 application for Windows that is intended to extract the private key from a PFX file containing an RSA cert/key bundle.
public static Boolean ToCertAndKey(String pfxFilePath, String? unlockPassword, String certFilePath, String keyFilePath, String? keyPassword, out String error) {
try {
error = String.Empty;
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
RSA key = bundle.GetRSAPrivateKey();
Byte[] publicKeyBytes = key.ExportSubjectPublicKeyInfo();
Byte[] privateKeyBytes;
//We fail here.
if (String.IsNullOrEmpty(keyPassword)) {
privateKeyBytes = key.ExportPkcs8PrivateKey();
} else {
privateKeyBytes = key.ExportEncryptedPkcs8PrivateKey(keyPassword,
new PbeParameters(
PbeEncryptionAlgorithm.Aes256Cbc,
HashAlgorithmName.SHA256,
iterationCount: 1));
}
String encodedCert = new(PemEncoding.Write("PUBLIC KEY", publicKeyBytes));
File.WriteAllText(certFilePath, encodedCert);
String encodedKey = new(PemEncoding.Write("PRIVATE KEY", privateKeyBytes));
File.WriteAllText(keyFilePath, encodedKey);
return true;
} catch (Exception ex) {
error = $"An exception occurred: '{ex.Message}'\r\n\r\nStack Trace:\r\n{ex.StackTrace}";
return false;
}
}
It fails at both ExportPkcs8PrivateKey (When I don't specify a password to encrypt the key) and ExportEncryptedPkcs8PrivateKey (when I do) with the same exception text:
WindowsCryptographicException: The requested operation is not supported
I came across this answer however, I'm still receiving the same exception at RSA.ExportEncryptedPkcs8PrivateKey.
There doesn't appear to be anything wrong with the PFX files I've been testing with; I'm able to import them into my certstore via the UI or PowerShell with no issues.
Hoping someone else has run into this issue.
You need to mark the keys as exportable.
Change
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword);
to
using var bundle = new X509Certificate2(pfxFilePath, unlockPassword, X509KeyStorageFlags.Exportable);
I am trying to sign some data using the private key from the smart card. The key algorithm is ECDSA. when I try to get the private key object it occurs system not supported exception.
Then after some research, I get to know that X509Certificate2 is not supporting EC Keys.
sysSec.X509Certificate2 cert = CertHelper.GetSignCertificate(serialNumber); //Get Certificate from Store var
key = cert.PrivateKey;
Then i try to use Bouncy Castle library. But in here i couldn't get ECPrivateKeyParameters after parsing X509Certificate2 . There is a code :
byte[] pkcs12Bytes = cert.Export(sysSec.X509ContentType.Pkcs12,"test");
Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build();
pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "test".ToCharArray());
ECPrivateKeyParameters privKey = null;
foreach (string alias in pkcs12.Aliases)
{
if (pkcs12.IsKeyEntry(alias))
{
privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key;
break;
}
}
It also not works. But strange things happen when I create CMS file. It works.
public byte[] Sign(byte[] data , X509Certificate2 certificate ,bool detached )
{
if (data == null)
throw new ArgumentNullException("data");
if (certificate == null)
throw new ArgumentNullException("certificate");
// setup the data to sign
// ContentInfo content = new ContentInfo( new Oid("1.3.14.3.2.26"), data);
ContentInfo content = new ContentInfo( data);
SignedCms signedCms = new SignedCms(content, detached);
CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, certificate);
signer.SignedAttributes.Add(new Pkcs9DocumentName("testname"));
signer.SignedAttributes.Add(new Pkcs9SigningTime());
//signer.;
// CmsRecipientCollection recipest =new CmsRecipientCollection ()
// create the signature
signedCms.ComputeSignature(signer);
// signedCms.ComputeSignature()
byte[] res = signedCms.Encode();
foreach (SignerInfo info in signedCms.SignerInfos)
{
foreach (var item in info.SignedAttributes)
{
string frname = item.Oid.FriendlyName ?? "null";
Console.WriteLine(string.Format(" OID {0} : Value {1}", frname, item.Oid.Value.ToString()));
}
foreach (var item in info.UnsignedAttributes)
{
string frname = item.Oid.FriendlyName ?? "null";
Console.WriteLine(string.Format(" OID {0} : Value {1}", frname, item.Oid.Value.ToString()));
}
}
Console.WriteLine("Signed !");
return res;
}
So do anyone knows how to handle it?
Also how to sign from smartCard using Bouncy Castle?
According to my understanding BouncyCastle is a cryptographic library. It can sign something, if you provide the key. Smart cards however don't typically export private keys (so I have some doubts, whether your certificate contains the one from the smart card) but expect commands to sign something, e. g. by receiving the respective hash value and returning the signature (after ensuring appropriate user authentication).
This is typically accomplished using a PKCS#11 interface (assumed you have a driver for it matching the command set of your card) or by sending the appropriate command APDUs directly to the card (quite complicated) from your application. I found nothing on the bouncy castle website, suggesting that there is some support for addressing smart cards. (It may be hidden in the OpenPGP functionality, if your card is compliant to that standard.)
So without being acquainted with BouncyCastle it seems to me, that it won't match your expectations.
Well, most of the questions/answers I've found here are regarding not caching a Smartcard PIN which is the opposite case of what I'm looking for.
We have a console application that signs multiple hashes. For this we use Pkcs.CmsSigner because we need to validate the signed hashes server-side.
Normally a Smartcard's PIN should be cached automatically in the CSP per process and it does in Windows 7, but if we run our code in W10 it does not. Also we support both CNG and non-CNG certificates.
The method we use to sign is the following:
public string SignX509(string data, bool chkSignature, string timestampServer, X509Certificate2 selectedCertificate)
{
CmsSigner oSigner = null;
SignedCms oSignedData = null;
string hashText = String.Empty;
try
{
if (chkSignature)
{
oSigner = new CmsSigner();
oSigner.Certificate = selectedCertificate;
byte[] arrDataHashed = HashSHA1(data);
// hash the text to sign
ContentInfo info = new ContentInfo(arrDataHashed);
// put the hashed data into the signedData object
oSignedData = new SignedCms(info);
if (string.IsNullOrEmpty(timestampServer)) {
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
}
else {
TimeStampToken tsToken = GetTSAToken(arrDataHashed, timestampServer);
AsnEncodedData timeData = new Pkcs9AttributeObject(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdAASigningCertificate.Id, tsToken.GetEncoded());
oSigner.UnsignedAttributes.Add(timeData);
oSigner.SignedAttributes.Add(new Pkcs9SigningTime(tsToken.TimeStampInfo.GenTime.ToLocalTime()));
}
// sign the data
oSignedData.ComputeSignature(oSigner, false);
hashText = Convert.ToBase64String(oSignedData.Encode());
}
else
{
// just clean the hidden hash text
hashText = String.Empty;
}
}
catch (Exception ex)
{
Console.WriteLine("ERRNO [" + ex.Message + " ]");
return null;
}
return hashText;
}
What we've tried so far:
Using RSACryptoServiceProvider to explicitly persist the key in the CSP
RSACryptoServiceProvider key = (RSACryptoServiceProvider)cmsSigner.Certificate.PrivateKey;
key.PersistKeyInCsp = true;
This works if we use the SignHash method but as I've said before, we need to verify the signed data server-side and we do not have access to the certificate, therefore we need a PKCS envelope. If I set this bool and sign using the CMS code the behaviour is the same.
Setting the PIN programmatically
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this approach I am able to disable the PIN prompt by setting the PIN programmatically. The problem here is that I have to read the PIN the first time so I can set it in the subsequent signatures.
I've tried to read the PIN from the prompt using CryptoGetProvParam with the dwParam PP_ADMIN_PIN and PP_KEYEXCHANGE_PIN but without luck. My two guesses here are:
I'm not reading in the right time or way
CMS uses a different handler internally
Question 1:
Is there any way to read the PIN set in the Windows prompt?
Question 2:
If reading the PIN is not possible, is there any other way to force PIN caching?
Only now realized this question is still without an answer although we managed to bypass the whole 'read the PIN from Windows prompt' question.
This method does not answer my first question but I'll be answering the second question.
There was a bug in the smartcard CSP provider that disabled the PIN cache for all requests to SignHash even though they were made in the same process.
The smartcard provider has a SDK that exposes some smartcard operations, being one of those an operation to validate the smartcard PIN.
What we ended up doing was to create a simple WPF window that requests the user's PIN and uses the SDK to validate the PIN. If it is correct we use the method that I posted in the original question to force the PIN cache:
Another try was setting the PIN programmatically via CryptoContext, based on this answer:
private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {
if (certificate == null) throw new ArgumentNullException("certificate");
var key = (RSACryptoServiceProvider)certificate.PrivateKey;
var providerHandle = IntPtr.Zero;
var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);
// provider handle is implicitly released when the certificate handle is released.
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
key.CspKeyContainerInfo.KeyContainerName,
key.CspKeyContainerInfo.ProviderName,
key.CspKeyContainerInfo.ProviderType,
SafeNativeMethods.CryptContextFlags.Silent));
SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
SafeNativeMethods.CryptParameter.KeyExchangePin,
pinBuffer, 0));
SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
certificate.Handle,
SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
0, providerHandle));
}
With this we are able to request the PIN only one time when signing multiple hashes until the smartcard provider fixes the bug on their side.
I'm running into the following and after feeling like I've exhausted various avenues of research on Google and Stack Overflow I decided to just ask my own question about it.
I'm trying to generate a personal certificate (using BouncyCastle) based on a CA certificate that I already have and own. After generating the certificate, placing it in the 'My' store, I then attempt to update my IIS website's SSL binding to use this new certificate.
What I'm noticing is that the updates to the IIS website (using ServerManager) are not throwing exception, yet when I go to the IIS Manager console I notice the website's binding has no SSL certificate selected. When I attempt to select the certificate that I created (shows up fine as a viable option) I get the following error message:
A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)
As a test I exported my generated certificate (with the private key) and reinstalled it via the wizard and then once again tried setting up the binding (via IIS Manager) which worked.
Because of this behavior I assumed it was an issue with how I was generating or adding the certificate to the store. I was hoping someone may have some idea of what the issue I'm having may be. The following are the relevant functions (I believe) used in creating the certificate, adding it to the store, and updating the website's binding programmatically:
Main function the generates that get the CA certificate private key, generates the personal self-signed certificate, and updates the sites binding:
public static bool GenerateServerCertificate(
X509Certificate2 CACert,
bool addToStore,
DateTime validUntil)
{
try
{
if (CACert.PrivateKey == null)
{
throw new CryptoException("Authority certificate has no private key");
}
var key = DotNetUtilities.GetKeyPair(CACert.PrivateKey).Private;
byte[] certHash = GenerateCertificateBasedOnCAPrivateKey(
addToStore,
key,
validUntil);
using (ServerManager manager = new ServerManager())
{
Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();
if (site == null)
{
return false;
}
foreach (Binding binding in site.Bindings)
{
if (binding.Protocol == "https")
{
binding.CertificateHash = certHash;
binding.CertificateStoreName = "MY";
}
}
manager.CommitChanges();
}
}
catch(Exception ex)
{
LOG.Error("Error generating certitifcate", ex);
return false;
}
return true;
}
Generating the certificate based on the CA private key:
public static byte[] GenerateCertificateBasedOnCAPrivateKey(
bool addToStore,
AsymmetricKeyParameter issuerPrivKey,
DateTime validUntil,
int keyStrength = 2048)
{
string subjectName = $"CN={CertSubjectName}";
// Generating Random Numbers
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom random = new SecureRandom(randomGenerator);
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);
// The Certificate Generator
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage,
true,
new ExtendedKeyUsage((new List<DerObjectIdentifier> { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));
// Serial Number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
X509Name subjectDN = new X509Name(subjectName);
X509Name issuerDN = new X509Name(CACertificateName);
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
// Valid For
DateTime notBefore = DateTime.UtcNow.Date;
DateTime notAfter = validUntil > notBefore ? validUntil : notBefore.AddYears(1);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Generating the Certificate
Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
// correcponding private key
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
X509Certificate2 x509 = new X509Certificate2(certificate.GetEncoded());
Asn1Sequence seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus,
rsa.PublicExponent,
rsa.PrivateExponent,
rsa.Prime1,
rsa.Prime2,
rsa.Exponent1,
rsa.Exponent2,
rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
if (addToStore)
{
// Add certificate to the Personal store
AddCertToStore(x509, StoreName.My, StoreLocation.LocalMachine, "Certificate Friendly Name");
}
return x509.GetCertHash();
}
Adding the certificate to the store:
private static void AddCertToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation, string friendlyName)
{
X509Store store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
if (!string.IsNullOrWhiteSpace(friendlyName)) {
var certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, cert.Subject, true);
if (certs.Count > 0)
{
certs[0].FriendlyName = friendlyName;
}
}
}
finally
{
store.Close();
}
}
Just a final note, I have tried a few things from what I've seen on various sites in regards to that error (doesn't seem very clear what the issue is):
This works on a different box (my personal development machine) but I hit these snags on a server machine (running Windows Server 2012 R2)
The IIS Help dialog informs me the machine is running IIS 8.5
Verified the validity generated certificate and the CA certificate with CertUtil.exe
Verified the generated certificate and the CA certificate had a private key that could be found
Verified administrators (and eventually even my logged in account) had access to where the private key file for both the CA certificate and the generated certificate.
Any ideas what my issue could be?
Update:
I was able to get some results by doing the following:
Export my certificate to a file programmatically by doing File.WriteAllBytes(filePath, cert.Export(X509ContentType.Pkcs12, password));
Then I import this certificate file to the store by doing:
var cert = new X509Certificate2(certFilePath, certPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// My original AddCertToStore function
AddCertToStore(cert, StoreName.My, StoreLocation.LocalMachine, "Friendly Name");
Finally, I set the binding as I was doing earlier:
using (ServerManager manager = new ServerManager())
{
Site site = manager.Sites.Where(q => q.Name == "My Site").FirstOrDefault();
if (site == null)
{
return false;
}
foreach (Binding binding in site.Bindings)
{
if (binding.Protocol == "https")
{
binding.CertificateHash = certHash;
binding.CertificateStoreName = "MY";
}
}
manager.CommitChanges();
}
Doing it this way works, but I don't see why I would have export the certificate to a file, THEN load it into a X509Certificate2 object, add to the store, and finally set up the binding.
The ToRSA method most likely creates an ephemeral RSA key, so when the references are all gone the key gets deleted. Exporting the ephemeral structure into a PFX then re-importing it with PersistKeySet is one way to turn it into a persisted key. Others exist, but that one is one of the less convoluted ones.
You don't actually have to write it to a file, though.
byte[] pkcs12Blob = cert.Export(X509ContentType.Pkcs12, password);
ver certWithPersistedKey = new X509Certificate2(pkcs12Blob, password, allTheFlagsYouAlreadySet);
There are also other subtleties going on, like setting the PrivateKey property has different behaviors for a cert instance that was loaded from a store and one which was loaded from bytes... the PFX/PKCS#12 export/import works around all of those.
For us, it was related to an invalid certificate. We went to IIS >> Server Certificates and exported the certificate from there.
The certificate was correctly bound to IIS site after that.
I am working on a project where I need to use a "public key" to encrypt a message using RSA algorithm. I was provided with a certificate and my first thought was to use Public Key from that certificate and after investigation I learned I need to use RSACryptoServiceProvider for encryption.
I have checked msdn and only method I thought I should use is RSACryptoServiceProvider.ImportCspBlob(byte[] keyBlob).
When I tried to use public key exported from certificate I was getting an error that the header data for certificate is invalid.
I know I can cast X509certificate2.PublicKey.Key to RSACryptoServiceProvider but from what I understood from my client is that going forward I will be given only a public key and not the certificate. This key will have to be saved in .xml configuration file.
So to summarize: Is there a way to generate an RSACryptoServiceProvider given only a certificate's public key?
You can try to look at this example: RSA public key encryption in C#
var publicKey = "<RSAKeyValue><Modulus>21wEnTU+mcD2w0Lfo1Gv4rtcSWsQJQTNa6gio05AOkV/Er9w3Y13Ddo5wGtjJ19402S71HUeN0vbKILLJdRSES5MHSdJPSVrOqdrll/vLXxDxWs/U0UT1c8u6k/Ogx9hTtZxYwoeYqdhDblof3E75d9n2F0Zvf6iTb4cI7j6fMs=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var testData = Encoding.UTF8.GetBytes("testing");
using ( var rsa = new RSACryptoServiceProvider(1024))
{
try
{
// client encrypting data with public key issued by server
//
rsa.FromXmlString(publicKey);
var encryptedData = rsa.Encrypt(testData, true);
var base64Encrypted = Convert.ToBase64String(encryptedData);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
You are OK and following a good typical pattern. The Sender of the data does not need the private key.
The following may confirm some of the code you already have figured out.
The one line where I set the private key for the receiver/decoder I left out.
I took this from a test case I have in my build deploy stuff.
byte[] certBytAr; // This is the certificate as bianry in a .cer file (no private key in it - public only)
X509Certificate2 cert2 = new X509Certificate2(certBytAr);
string strToEncrypt = "Public To Private Test StackOverFlow PsudeoCode. Surfs Up at Secret Beach.";
byte[] bytArToEncrypt = Encoding.UTF8.GetBytes(strToEncrypt);
RSACryptoServiceProvider rsaEncryptor = (RSACryptoServiceProvider)cert2.PublicKey.Key;
byte[] dataNowEncryptedArray = rsaEncryptor.Encrypt(bytArToEncrypt, true);
// done - you now have encrypted bytes
//
// somewhere elxe ...
// this should decrpyt it - simulate the destination which will decrypt the data with the private key
RSACryptoServiceProvider pk = // how this is set is complicated
// set the private key in the x509 oobject we created way above
cert2.PrivateKey = pk;
RSACryptoServiceProvider rsaDecryptor = (RSACryptoServiceProvider)cert2.PrivateKey;
byte[] dataDecrypted = rsaDecryptor.Decrypt(dataNowEncryptedArray, true);
Console.WriteLine(" encrypt 1 Way Intermediate " + BitConverter.ToString(dataDecrypted));
string strDecodedFinal = Encoding.UTF8.GetString(dataDecrypted);
if (strDecodedFinal == strToEncrypt)
{
}
else
{
Console.WriteLine(" FAILURE OF ENCRYPTION ROUND TRIP IN SIMPLE TEST (Direction: Public to Private). No Surfing For You ");
}