Recently I needed to encrypt data on a server and allow a limited number of service accounts the ability to decrypt that data so it was as safe as possible.
The approach I took to achieve this was by using a X509 certificate and it's ability to allow you to encrypt information via it's public key and decrypt that information through the private key.
The key parts of this approach are:
- Create a certificate
- Ensure the KeySpec of the certificate is set up correctly to allow for encryption e.g. "KeyExchange" or "None" if you are doing this via PowerShell
- Set the security on the private keys so only specific user accounts can access it and decrypt information encrypted via the public key.
Step 1 - Create a certificate to use
The easiest way to get a quick example going is via PowerShell to create a dummy root certificate and the one we will use for encrypting and decrypting.
$rootCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "BlogSuperRootCA" -TextExtension @("2.5.29.19={text}CA=true") -KeyUsage CertSign,CrlSign,DigitalSignature
$testCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "SignedByBlogSuperRootCA" -KeyExportPolicy Exportable -KeyLength 2048 -KeyUsage DigitalSignature,KeyEncipherment -Signer $rootCert -KeySpec "None"
Step 2 - Secure your private keys
Once you have the certificate created you can then easily secure the private key via PowerShell
$accounts = @("someDomain\userA","someDomain\userB")
$fileName = $testCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$fileName"
$acl = Get-Acl -Path $path
$acl.SetAccessRuleProtection($True, $False)
foreach($account in $accounts)
{
Write-Output "Adding $account"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($account,"Full","Allow")
$acl.AddAccessRule($rule)
}
Set-Acl -Path $path -AclObject $acl
Now we have our certificate and the permissions all set we can move onto the C# code which enables the encryption and decryption!
Step 3 - Use the certificate to encrypt and decrypt
Once you have the certificate and the permissions setup for who has access to the private key it's very straight forward to do the encryption and decryption. I've included below a simple test that should enable you to test this out yourself without much trouble.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
namespace Blog.Michael.Ciba.CertEncryptionTesting
{
[TestFixture]
public class Given_a_valid_certificate_with_a_private_key_we_can_access
{
private X509Certificate2 _certificate;
[OneTimeSetUp]
public void Using()
{
const string BlogCertificateSubject = "CN=SignedByBlogSuperRootCA";
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
_certificate = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName,
BlogCertificateSubject, false)
.Cast()
.FirstOrDefault();
}
}
[Test]
public void It_will_encrypt_and_decrypt_the_text_and_not_throw_an_exception()
{
// Arrange
const string TextToEncrypt = "Some random text to encrypt";
// Act
var encryptedText = GetEncryptedTextBasedOnCertificatePublicKey(_certificate, TextToEncrypt);
var actualDecryptedText = GetDecryptedTextBasedOnCertPrivateKey(_certificate, encryptedText);
// Assert
Assert.That(encryptedText, Is.Not.EqualTo(TextToEncrypt));
Assert.That(actualDecryptedText, Is.EqualTo(TextToEncrypt));
}
public static string GetEncryptedTextBasedOnCertificatePublicKey(X509Certificate2 certificate,
string textToEncrypt)
{
var rsa = certificate.GetRSAPublicKey();
var bytesToEncrypt = Encoding.UTF8.GetBytes(textToEncrypt);
var encryptedBytes = rsa.Encrypt(bytesToEncrypt, RSAEncryptionPadding.OaepSHA512);
var encryptedText = Convert.ToBase64String(encryptedBytes);
return encryptedText;
}
public static string GetDecryptedTextBasedOnCertPrivateKey(X509Certificate2 certificate,
string textToDecrypt)
{
var rsa = certificate.GetRSAPrivateKey();
var bytesToDecrypt = Convert.FromBase64String(textToDecrypt);
var decryptedBytes = rsa.Decrypt(bytesToDecrypt, RSAEncryptionPadding.OaepSHA512);
var decryptedText = Encoding.UTF8.GetString(decryptedBytes);
return decryptedText;
}
}
}
See that wasn't too complex was it?
Comments
Post a Comment