When using RSACryptoServiceProvider in i.e. ASP.NET you may get the following exception under a heavy load scenario:
“System.Security.Cryptography.CryptographicException: CryptoAPI cryptographic service provider (CSP) for this implementation could not be acquired”.
In this case we can see that RSACryptoServiceProvider constructor calls CryptAcquireContext API. The first call to CryptAcquireContext API fails with error NTE_BAD_KEYSET (“Keyset does not exist“). Then a second call is attempted to create the key set, but it fails with error NTE_EXISTS (“Object already exists“). So it seems the keyset is there, but it can’t be accessed. Concurrency issues? Maybe. Check this article first to verify that we don’t get these errors for another reason: CryptAcquireContext fails with NTE_BAD_KEYSET.
So we are having concurrency issues. Consider the application performs the following sequence in two steps:
1) Open a given named machine key container.
2) If open fails, create that named machine key container.
This sequence is not an automatic operation. If multiple threads are attempting to do the same sequence and assume 2 threads attempt to create the same named machine key container, the second call will fail with “key container already exists”.
As we’ve seen this is what RSACryptoServiceProvider constructor does similar to any application calling CryptAcquireContext API twice.
If the application is always working with the same RSA key in a named key container, key containers must be acquired only once in the process using CryptAcquireContext. When multiple threads are using the same RSA instance for encryption/decryption, the instance methods are not thread safe.
At the CryptoAPI level we’ve already seen threading issues when dealing with key handles in applications. When calling CryptoAPI directly we have a lot more control when the key handle is obtained, the type of handle, how it is used and whether it is thread safe. e.g. After a RSA key handle has been obtained by using the CryptGetUserKey function, RSA operations are thread safe.
There is not much control when using RSACryptoServiceProvider as it doesn’t expose key handles.
These are some suggestions to minimize threading issues in ASP.NET:
2) Explicitely call Dispose on the RSACrytpoServiceProvider object when possible. This way, we don’t rely on the Garbage Collector to “eventually” release the CSP.
3) Set UseMachineKeyStore in CspParameters.Flags when creating an RSACryptoServiceProvider. This flag tells RSACryptoServiceProvider to use “machine location” for storing RSA public/private key pair instead of the “current user” location. This is equivalent to calling CryptAcquireContext API with CRYPT_MACHINE_KEYSET. Make sure all the threads using the RSA instance are running under the same account.
4) Don’t set RSACryptoServiceProvider.PersistKeyInCSP to False. This will cause the key container to be deleted when RSA instance is released or garbage collected. If multiple threads are trying to access the same named key container, the state of that named key container could be in a temporary cleaned up state and it cannot be acquired.
5) Another option would be for us to instantiate RSACryptoServiceProvider once as part of Web Application initialization and then use that same instance for encryption or decryption under a critical section like lock (i.e. Mutex). This would also avoid reacquiring key container multiple times from the same process.
I hope this helps.
Alex (Alejandro Campos Magencio)