RSACryptoServiceProvider fails when used with ASP.NET
Hi, welcome back,
I will talk today about a very common issue we face when we try to use .NET's RSACryptoServiceProvider class in ASP.NET.
When we try to create a new RSACryptoServiceProvider object in this scenario, we may get the following exception:
"System.Security.Cryptography.CryptographicException: The system cannot find the file specified".
By using my CryptoAPI Tracer script we can take a look to the CryptoAPI calls that .NET is making behind the scenes. Thanks to this script we will be able to see the exact API that is failing and the exact error (which most of the time .NET masks).
In our case, the API that fails is CryptAcquireContext, and it fails with error #2 (ERROR_FILE_NOT_FOUND). According to CryptAcquireContext documentation, this error means the following:
The profile of the user is not loaded and cannot be found. This happens when the application impersonates a user, for example, the IUSR_ComputerName account.
By default, ASP.NET won't load the user profile. Take a look to the parameters of the problematic CryptAcquireContext call as being shown in the log file that my script generated. If this API is not being called with CRYPT_MACHINE_KEYSET (to use the machine profile) or CRYPT_VERIFYCONTEXT (to use temporary key stores), it will try to access the key stores in the user profile, and it will fail because its not loaded.
Which options do we have here then?
1) If we need each user running your ASP.NET app to use their own key stores, we will have to load their user profile. We can do it with LoadUserProfile API. The only problem is that the privileges needed to execute this API are quite powerful for a standard user.
2) If we still need the user profile and don't want to give users enough privileges to run LoadUserProfile, there is a trick we can use: Service Control Manager (SCM) will automatically load the profile of the user that a Windows service uses to run. So we may create a dummy Windows service which runs with our user's credentials. This service won't need to do anything special, just stay alive. SCM will load the user profile for us and ASP.NET will be able to use it automatically once it gets loaded.
3) We may also use machine key stores instead of user's. We just have to pass CRYPT_MACHINE_KEYSET flag to CryptAcquireContext. In order to do that in .NET, we may use a code like this:
CspParameters RSAParams = new CspParameters(); RSAParams.Flags = CspProviderFlags.UseMachineKeyStore; RSACryptoServiceProvider sp = new RSACryptoServiceProvider(1024, RSAParams);
Note. When we use machine key stores, any user in that machine may have access to those keys.
I hope this helps.
Alex (Alejandro Campos Magencio)