Visual C#: RSA encryption using certificate
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.
https://wimbeck.be/wp-content/uploads/2015/03/rsa_template.png
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.
https://wimbeck.be/wp-content/uploads/2015/03/rsa_privKey.png
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
- RSACryptoServiceProvider
- New-SelfSignedCertificate
- Self-signed certificate generator (PowerShell)
- Eight tips for working with X.509 certificates in .NET
- IX509PrivateKey interface
- Get-PrivateKeyPath