Originally posted at: Visual C#: RSA encryption using certificate @ IS4U Blog
Intro
RSA is a well-known cryptosystem using assymetric 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 not 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.
Generate self-signed certifiate
Windows Server 2012 R2 provides a cmdlet, New-SelfSignedCertificate
, to generate a certificate. However, this cmdlet does not provide sufficient parameters to generate a certificate that can be used in C#. I used following script to generate my 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 my search for the correct value for the ProviderName parameter, I came across the interface of IX509PrivateKey
, which provides a LegacyCsp boolean flag. I added following $PrivateKey.LegacyCsp = $true
in #region Private Key
[line 327]. After this addition, following powershell command gave me a certificate I could use to perform 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.
Alternatively, you can script the process using an extra module to find the private key location:
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 public key can be published and/or transported to a partner that needs to send sensitive data. To export the certificate with public key execute 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
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
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
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; }