Introduction

RSA is a well-known cryptosystem using asymmetric encryption. It performs encryption using a public key, decryption using a private key. The private key should be protected. The most efficient way of managing these keys in a Windows environment is by using certificates. To protect the private key, you should make it non-exportable. This way the private key is only available on the machine it is being used.


Create certificate

Request certificate from CA


When enrolling for a certificate, make sure that the template has the Legacy Cryptographic Service Provider selected. Otherwise .Net will not be able to use the certificate. It will crash with this exception:
Unhandled Exception: System.Security.Cryptography.CryptographicException: Invalid provider type specified.
rsa_template

Generate self-signed certificate


Windows Server 2012 R2 provides a cmdlet, New-SelfSignedCertificate, to generate a certificate. The cmdlet does not provide sufficient parameters to generate a certificate that can be used in C#. The following script can be used to generate a self-signed certificate New-SelfsignedCertificateEx:
This script is an enhanced open-source PowerShell implementation of deprecated makecert.exe tool and utilizes the most modern certificate API — CertEnroll.
In a search for the correct value for the ProviderName parameter, you may notice the interface of IX509PrivateKey, which provides a LegacyCsp boolean flag. After adding $PrivateKey.LegacyCsp = $true in #region Private Key [line 327], the following PowerShell command resulted in a certificate which can be used for RSA encryption and decryption:

param($certName)
Import-Module .\New-SelfSignedCertificateEx.psm1
New-SelfsignedCertificateEx -Subject "CN=$certName" -KeyUsage "KeyEncipherment, DigitalSignature" -StoreLocation "LocalMachine" -KeyLength 4096

Grant access to private key


The account(s) that will perform the decryption requires read access to the private key of the certificate. To configure this, open a management console (MMC). Add the certificates snap-in for the local computer. In the certificate store, right-click the certificate, go to all tasks and click Manage Private Keys. Add the account and select Read. Apply the changes.

 rsa_privKey

Alternatively, you can script the process using an extra module to find the private key location and granting read access via icacls:

param($certName, $user)
Import-Module .\Get-PrivateKeyPath.psm1
$privateKeyPath = Get-PrivateKeyPath CN=$certName -StoreName My -StoreScope LocalMachine
& icacls.exe $privateKeyPath /grant ("{0}:R" -f $user)


Export public key


The certificate with a public key can be published and/or transported to a partner who you will communicate sensitive data with. To export the certificate with public key execute the following script:

param($certName)
$Thumbprint = (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -match "CN=$certName"}).Thumbprint;
Export-Certificate -FilePath "C:\certName.crt" -Cert Cert:\LocalMachine\My\$Thumbprint

Do not forget to refresh the certificate keys on a regular basis.


Load certificate

The following code snippet shows how to locate and load the certificate.

using System;
using System.Security.Cryptography.X509Certificates;
 
private X509Certificate2 getCertificate(string certificateName)
{
    X509Store my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    my.Open(OpenFlags.ReadOnly);
    X509Certificate2Collection collection = my.Certificates.Find(X509FindType.FindBySubjectName, certificateName, false);
    if (collection.Count == 1)
    {
        return collection[0];
    }
    else if (collection.Count > 1)
    {
        throw new Exception(string.Format("More than one certificate with name '{0}' found in store LocalMachine/My.", certificateName));
    }
    else
    {
        throw new Exception(string.Format("Certificate '{0}' not found in store LocalMachine/My.", certificateName));
    }
}


Encryption

The following code snippet shows how to encrypt the input using a certificate.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
 
private string EncryptRsa(string input)
{
       string output = string.Empty;
       X509Certificate2 cert = getCertificate(certificateName);
       using (RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key)
       {
              byte[] bytesData = Encoding.UTF8.GetBytes(input);
              byte[] bytesEncrypted = csp.Encrypt(bytesData, false);
              output = Convert.ToBase64String(bytesEncrypted);
       }
       return output;
}


Decryption

The following code snippet shows how to decrypt the input using a certificate. Make sure that the account running this has read access to the private key.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
 
private string decryptRsa(string encrypted)
{
    string text = string.Empty;
    X509Certificate2 cert = getCertificate(certificateName);
    using (RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey)
    {
           byte[] bytesEncrypted = Convert.FromBase64String(encrypted);
           byte[] bytesDecrypted = csp.Decrypt(bytesEncrypted, false);
           text = Encoding.UTF8.GetString(bytesDecrypted);
    }
    return text;
}


References


Credits
Originally posted at: Visual C#: RSA encryption using certificate @ IS4U Blog