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.

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

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

  1. ravi says:

    Hi, i have tried your example and it’s working fine for encryption and decryption. The thing is when i clear the resources i.e.call delete method to delete key container, system is still able to encrypt and decrypt data using key container name. Can you please let me know if this is a valid behaviour?

    • Andras Nemes says:

      Hello, yes, the Clear method releases the resources associated with the asymmetric keys but the actual files will stay where they are. Clear() is like calling Close() on a file stream. //Andras

      • ravi says:

        Thanks for the quick response. I would like to know that is there any way to completely delete the key pairs from key container? Like once deleted it can not be used to encrypt decrypt data using same key container name.

      • ravi says:

        i save one key container with public key only and object for rsacryptoserviceproduct has publiconly proerty to true, but when i load this object in another program using key container name it shows publiconly to false, which means i can encrypt and decrypt data by saving only public key in key container. Can you tell me if this is a right behavior.

  2. Chris W. says:

    Can this method be used for Symetric key storage also? Or is it just for Asymetric?

  3. Gourav Agarwal says:

    Hi, Thanks you sharing such a blog, its really very helpful.

    My Question is
    Can be create password protected window keystore file

    I tried with CspParameters.KeyPassword but its give error

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

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