3

If you look at the code below it is doing nothing but creating a new instance of an RSACryptoServiceProvider. The key container name is initialized from a property that creates a name based on various parameters. I have added hardcoded values in this demo code.

The code is running on a Windows 2008 R2 server installation and has worked for months with a constant value being returned for the key container name.

Some days ago the code stopped working and we're facing the exception below. Using the key container name that has been used for months no longer works. The server has been rebooted, IIS has been restarted - no success. Only after changing the key name it has started to work again.

Can somebody explain why this is happening and how to fix it? As far as I can see, this code does not crete any persistent objects. Why would it still fail after a reboot? From MSDN (http://msdn.microsoft.com/de-de/library/ca5htw4f.aspx) I read that the constructor "constructor creates or reuses a key container specified using the KeyContainerName field". Does the "reuse" mean, it is caching the stuff somewhere and while doing that it crashed and is now stuck with a corrupt cached version? Also note that the same key name is currently still used on many other machines - no problems anywhere.

This is the line that is crashing:

using ( RSACryptoServiceProvider rsa = new RSACryptoServiceProvider( this.oCspParameters ) )
{
}

These are the CspParameters used:

private readonly CspParameters oCspParameters = new CspParameters
{
  Flags = CspProviderFlags.UseMachineKeyStore,
};

this.oCspParameters.KeyContainerName = oProfile.KeyName;

And that's the key name:

public string KeyName
{
    get
    {
        return string.Format( "API-{0}-v{1}", "TestClient", "1.0.0.0" );
    }
}

And finally the exception:

CryptographicException: An internal error occurred.
Service Operation: ISessionService.Identify #f173250b-d7ac-45d5-98ed-7fffcf37d95a
at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
Krumelur
  • 32,180
  • 27
  • 124
  • 263

2 Answers2

5

As far as I can see, this code does not crete any persistent objects

The following code will create the key container if it doesn't already exist:

using ( RSACryptoServiceProvider rsa = new RSACryptoServiceProvider( this.oCspParameters ) ) 
{ 
} 

If you want to force use of an existing key, you should specify:

cp.Flags = CspProviderFlags.NoPrompt | CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore;

Does the "reuse" mean, it is caching the stuff somewhere and while doing that it crashed and is now stuck with a corrupt cached version?

It will reuse a key stored in the Microsoft\Crypto\RSA\MachineKeys\ subfolder of the Environment.SpecialFolder.CommonApplicationData folder, if it exists and you have permission to access it. Otherwise it will attempt to create it.

Perhaps there is a key there that you don't have permission to access?

The file names of the key containers use a generated unique id (CspKeyContainerInfo.UniqueKeyContainerName), but you can examine the file contents with a text editor, and the key container name is in the first few characters of the file.

Once you have located the offending file, you can examine its permissions, and perhaps delete it so it can be recreated.

From comments:

How can I force it then to NOT do so or how can I delete the container?

You can delete a existing key container (provided you have the necessary permissions) when you've finished with it with the following code:

CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.NoPrompt | CspProviderFlags.UseExistingKey | 
                   CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = containerName;

using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
{
    rsa.PersistKeyInCsp = false;
}

I haven't tried it, but perhaps setting PersistKeyInCsp=false when creating the key would prevent if from being persisted.

However none of the above explains why you are no longer able to access a key that you were previously using successfully. The most obvious reason would be permissions - I know you get a CryptographicException if you attempt to access a key container that you're not authorized to access, but I don't know if you would expect to get a more explicit error message than "An internal error occurred". The first thing I'd do would be to check the ACL on the key container file. Perhaps you have two instances of your application running under different identities that attempt to create the key container - and the key container created by identity 1 is not accessible by identity 2.

Finally as you seem to suggest you don't want to persist the key container, why would you use the machine store?

Joe
  • 122,218
  • 32
  • 205
  • 338
  • No, I do NOT want to use an existing key. That's why no additional flags are specified. I'll check the folders anyway. – Krumelur Jul 04 '12 at 13:47
  • @Krumelur, AFAIK it will create the key container the first time you access it, and then subsequently reuse the same key container, even without setting the UseExistingKey flag. – Joe Jul 04 '12 at 15:59
  • How can I force it then to NOT do so or how can I delete the container? – Krumelur Jul 04 '12 at 20:21
  • Good question: why would I use the machine store. I don't know. Should I not specify ANY store? – Krumelur Jul 05 '12 at 07:17
  • @Krumelur, the default is the user profile store. If your app runs under an identity with a user profile (e.g. client app), and you don't want to share the key with anyone else, this would make more sense. For a server app which may run under an identity without a profile (e.g. Network Service) you'd need to use the machine store if you're persisting the key. – Joe Jul 05 '12 at 08:04
  • Also, use the machine store if you need to export the key and use the same key on another computer. – 27k1 May 23 '14 at 08:01
1

I recently discovered this question while helping another dev resolve this issue on his local machine.

In our scenario, we were using RSA to encrypt a password. The code in question had worked in the application for years. Suddenly, the RSACryptoServiceProvider constructor began to experience errors on one developer's machine. The message was the ambiguous and dreaded "Key not valid for use in specified state."

From Joe's answer above, we found the stored the keys in a location like:

C:\Users\<user name>\AppData\Roaming\Microsoft\Crypto\RSA\

By opening the key files in notepad++, we were able to find the name of our key at the top of one of the files.

Deleting the offending file allowed the developer to run the application successfully again - the file was regenerated the next time we called the constructor.

I am still not sure what happened, but it appears the original key file had somehow become corrupted.

SouthShoreAK
  • 4,176
  • 2
  • 26
  • 48