How to store the asymmetric keys in the Windows key store with C#

Introduction

In this post we briefly looked through asymmetric encryption in .NET. This encryption type requires two keys as opposed to symmetric encryption where the same key is used for encryption and decryption. In asymmetric encryption we have a public and a private key. The public key can be distributed so that other people can encrypt their messages to us. Then we use our private key to decrypt the ciphertext and read the original message. Therefore we don’t have to worry about the public key getting into the wrong hands. On the other hand asymmetric encryption is significantly slower than symmetric encryption due to the higher mathematical complexity.

In the post referenced above we saw how to store the asymmetric key-pair in an XML string. You can save this string in a file or database for later retrieval. There’s at least one more option for storage which is the cryptographic key store on Windows. We’ll go through how to use it in this post.

The RSA key-store

The Windows key store is a “normal” folder on disk, i.e. it is not some hidden and magic location that only computer professionals can locate. On my PC with Windows 10 the various key types are stored in the C:\ProgramData\Microsoft\Crypto folder. This folder includes subfolders for the various key types such as SystemKeys and RSA. Chances are that your RSA folder in turn has another folder called MachineKeys which contains a couple of system files. These files are how the machine level cryptographic keys are stored. “Machine level” means that they are accessible to anyone logged onto the machine. This is analogous to how digital certificates can be available on the user and machine level. Cryptographic keys can also be made available for a single user or the whole machine.

Each system file in this folder is associated with a key container name that we can refer to in code as we’ll see later. We’ll also see how to read the name and path of the crypto file we generate in code.

An important acronym that we need to remember here is CSP which stands for Cryptographic Service Provider. We’ll soon see it in action in form of the CspParameters class. This class provides a link between .NET and the underlying cryptographic services on Windows.

Demo objects

We’ll reuse some of the objects from the previous post mentioned above.

The various return types of the asymmetric encryption service will will derive from from an abstract base class to hold the outcome and any exception message:

public abstract class OperationResult
{
	public bool Success { get; set; }
	public string ExceptionMessage { get; set; }
}

The first task of the asymmetric service is to generate a key pair and save it the key store. The following object will hold the key container name and the path and folder name of the key file:

public class AsymmetricKeyPairPersistenceResult : OperationResult
{
	public string KeyContainerName { get; set; }
	public string KeyStorageFileFullPath { get; set; }
	public string KeyStorageTopFolder { get; set; }
}

We’ll also see how to release a key from the RSA service provider. I couldn’t come up with any additional return value for the deletion operation so here we only have an empty container class:

public class AsymmetricKeyPairDeletionResult : OperationResult
{
}

Furthermore we have the AsymmetricEncryptionResult and AsymmetricDecryptionResult classes which are identical to what we saw in the previous post, I won’t show them here again.

The service class

The following methods are all part of a class called AsymmetricEncryptionService. First let’s see how to generate and persist a new key pair in the key store:

public AsymmetricKeyPairPersistenceResult PersistNewAsymmetricKeyPair(int keySizeBits)
{
	AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = new AsymmetricKeyPairPersistenceResult();
	try
	{
		CspParameters cspParams = new CspParameters();
		string containerName = Guid.NewGuid().ToString();
		cspParams.KeyContainerName = containerName;
		cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
		RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits, cspParams)
			{ PersistKeyInCsp = true };
		CspKeyContainerInfo keyContainerInfo = new CspKeyContainerInfo(cspParams);
		string pathToMachineLevelAsymmetricKeysFolder = Path.Combine(
			Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
			@"Microsoft\Crypto\RSA\MachineKeys");
		asymmetricKeyPairPersistenceResult.KeyContainerName = keyContainerInfo.KeyContainerName;
		asymmetricKeyPairPersistenceResult.KeyStorageTopFolder = pathToMachineLevelAsymmetricKeysFolder;
		asymmetricKeyPairPersistenceResult.KeyStorageFileFullPath =
			Path.Combine(pathToMachineLevelAsymmetricKeysFolder, keyContainerInfo.UniqueKeyContainerName);
		asymmetricKeyPairPersistenceResult.Success = true;
	}
	catch (Exception ex)
	{
		asymmetricKeyPairPersistenceResult.ExceptionMessage = ex.Message;
	}
	return asymmetricKeyPairPersistenceResult;
}

We’ve already mentioned the CspParameters class. A container name is generated as a GUID and is assigned to the key container name property. We also declare that we want to use the machine level key store. We saw the RSACryptoServiceProvider class in action before and discussed the valid key sizes. The above code doesn’t check for the validity of the provided keySizeBits parameter since we went through it in the previous post. We pass in the key size and the CSP parameters to the RSACryptoServiceProvider constructor and set the PersistKeyInCsp property to true. The pathToMachineLevelAsymmetricKeysFolder variable assignment shows how to find the path to the machine level asymmetric key folder. Using CspKeyContainerInfo we can also get to the actual file name and key container name of the key-pair.

The next function shows how to release an asymmetric key from the key store:

public AsymmetricKeyPairDeletionResult DeleteAsymmetricKeyPair(string containerName)
{
	AsymmetricKeyPairDeletionResult asymmetricKeyPairDeletionResult = new AsymmetricKeyPairDeletionResult();
	try
	{
		CspParameters cspParams = new CspParameters() { KeyContainerName = containerName };
		RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };
		rsaProvider.Clear();
		asymmetricKeyPairDeletionResult.Success = true;
	}
	catch (Exception ex)
	{
		asymmetricKeyPairDeletionResult.ExceptionMessage = ex.Message;
	}
	return asymmetricKeyPairDeletionResult;
}

We load the CSP parameters using the key container name and instantiate an RSACryptoServiceProvider from it. We call RSACryptoServiceProvider.Clear() in order to release all resources used by the RSA service provider.

Encryption and decryption are very much the same as we saw before using XML key-pairs. The major difference is that we load the keys from the key store instead. Otherwise the implementations are identical:

public AsymmetricDecryptionResult DecryptWithCspProvider(byte[] cipherBytes, string keyContainerName)
{
	AsymmetricDecryptionResult asymmetricDecryptionResult = new AsymmetricDecryptionResult();
	try
	{
		CspParameters cspParams = new CspParameters() { KeyContainerName = keyContainerName };
		RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
		byte[] decryptBytes = rsaProvider.Decrypt(cipherBytes, true);
		asymmetricDecryptionResult.DecryptedMessage = Encoding.UTF8.GetString(decryptBytes);
		asymmetricDecryptionResult.Success = true;
	}
	catch (Exception ex)
	{
		asymmetricDecryptionResult.ExceptionMessage =
			$"Exception caught while decrypting the cipher: {ex.Message}";
	}
	return asymmetricDecryptionResult;
}

public AsymmetricEncryptionResult EncryptWithCspProvider(string message, string keyContainerName)
{
	AsymmetricEncryptionResult asymmetricEncryptionResult = new AsymmetricEncryptionResult();
	try
	{
		CspParameters cspParams = new CspParameters() { KeyContainerName = keyContainerName };
		RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
		byte[] encryptedAsBytes = rsaProvider.Encrypt(Encoding.UTF8.GetBytes(message), true);
		string encryptedAsBase64 = Convert.ToBase64String(encryptedAsBytes);
		asymmetricEncryptionResult.EncryptedAsBase64 = encryptedAsBase64;
		asymmetricEncryptionResult.EncryptedAsBytes = encryptedAsBytes;
		asymmetricEncryptionResult.Success = true;
	}
	catch (Exception ex)
	{
		asymmetricEncryptionResult.ExceptionMessage =
			$"Exception caught while encrypting the message: {ex.Message}";
	}
	return asymmetricEncryptionResult;
}

Test code

The following function pulls all of the above classes together:

private static void TestAsymmetricEncryptDescryptWithCsp()
{
	string NL = Environment.NewLine;
	AsymmetricEncryptionService asymmetricEncryptionService = new AsymmetricEncryptionService();
	int keySizeBits = 2048;
	AsymmetricKeyPairPersistenceResult asymmetricKeyPairPersistenceResult = 
		asymmetricEncryptionService.PersistNewAsymmetricKeyPair(keySizeBits);
	if (asymmetricKeyPairPersistenceResult.Success)
	{
		Console.WriteLine($"Asymmetric key-pair persistence success.{NL}Container name: {asymmetricKeyPairPersistenceResult.KeyContainerName}{NL}Top key file folder: {asymmetricKeyPairPersistenceResult.KeyStorageTopFolder}{NL}Key-pair file full path: {asymmetricKeyPairPersistenceResult.KeyStorageFileFullPath}{NL}");
		string originalMessage = "This is an extremely secret message.";
		AsymmetricEncryptionResult asymmetricEncryptionResult = 
			asymmetricEncryptionService.EncryptWithCspProvider(originalMessage, 
				asymmetricKeyPairPersistenceResult.KeyContainerName);
		if (asymmetricEncryptionResult.Success)
		{
			Console.WriteLine("Encryption success.");
			Console.WriteLine($"Cipher text: {NL}{asymmetricEncryptionResult.EncryptedAsBase64}{NL}");
			AsymmetricDecryptionResult asymmetricDecryptionResult =
				asymmetricEncryptionService.DecryptWithCspProvider
				(asymmetricEncryptionResult.EncryptedAsBytes, asymmetricKeyPairPersistenceResult.KeyContainerName);
			if (asymmetricDecryptionResult.Success)
			{
				Console.WriteLine("Decryption success.");
				Console.WriteLine($"Deciphered text: {NL}{asymmetricDecryptionResult.DecryptedMessage}");
			}
			else
			{
				Console.WriteLine($"Decryption failed.{NL}{asymmetricDecryptionResult.ExceptionMessage}");
			}
		}
		else
		{
			Console.WriteLine($"Encryption failed.{NL}{asymmetricEncryptionResult.ExceptionMessage}");
		}
		AsymmetricKeyPairDeletionResult keyPairDeletionResult = 
			asymmetricEncryptionService.DeleteAsymmetricKeyPair(asymmetricKeyPairPersistenceResult.KeyContainerName);
		if (keyPairDeletionResult.Success)
		{
			Console.WriteLine("Resources released.");
		}
		else
		{
			Console.WriteLine($"Resource release failed: {keyPairDeletionResult.ExceptionMessage}");
		}
	}
	else
	{
		Console.WriteLine($"Asymmetric key-pair persistence failed.{NL}{asymmetricKeyPairPersistenceResult.ExceptionMessage}");
	}
}

Here’s an example output:

Asymmetric key-pair persistence success.
Container name: c78273d5-6959-4ed8-a72e-62602839e80d
Top key file folder: C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Key-pair file full path: C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d4fccdb04b63b08b297451115557787c_461b8e32-b72b-4e79-89c7-2530e26cd09d

Encryption success.
Cipher text:
b30QNTghGI1iuVQUK8wHYHvlMiboHMsJ2CxWMEJCP0Rikrg9bjiT9EQ7jynjxmF4L3EOUPmaOTtE4ElStZY/sp9zjoIzRJzeHw+pU2WHOZYh6506YRLkktXx2ZeompRCbLVBjR1QwTcfHWf6GDpHUxwLwF01p3oIp43hbPZrhwk=

Decryption success.
Deciphered text:
This is an extremely secret message.
Resources released.

You can view the list of posts on Security and Cryptography here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: