Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.

Posts on this page:

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/08/04/how-to-modify-an-interop-assembly-to-change-the-return-type-of-a-method-vb-net/
Post name: How to modify an Interop assembly to change the return type of a method (VB.NET)
Original author: Alejandro Campos Magencio
Posting date: 2008-08-04T02:52:00+00:00


Hi all,


In some situations we may need to reference a COM dll in our Visual Studio project in order to use a specific COM object within our .NET application, and thanks to COM Interop we can do it. When we add a reference to a COM dll in our project, a COM Interop assembly gets generated so we can use COM objects in the dll almost like any other .NET object. And I say almost because i.e. .NET needs to translate COM types to .NET types and it doesn't always do the conversion the way we like or need.


Let's see this with the following real example:


We are writing an Exit Module for our Certification Authority. We are trying to obtain the raw binary data for a certificate issued by our CA. We are calling CCertServerExit.GetCertificateProperty("RawCertificate", PROPTYPE_BINARY) (ICertServerExit::GetCertificateProperty Method) to achieve that. But GetCertificateProperty is returning a String and not an array of bytes, because .NET interop layer is converting a VARIANT of type BSTR (vt = VT_BSTR) to a .NETString. Because of this return type, .NET is trimming the last byte of the binary certificate when the number of bytes is odd. .NET strings cannot represent BSTRs that contain an odd number of bytes.


Fortunately we can modify the Interop assembly and choose a different return type for that method. This modification will allow us to get all the bytes in the certificate. These are the steps to do this:


1) Extract IL from Interop.CertClientLib.dll interop assembly generated by VS IDE when we added CertCli COM reference to our VS project:

ildasm.exe Interop.CertClientLib.dll /out:temp.il

2) Delete Interop.CertClientLib.dll file.


3) Delete CertCli COM reference from our project.

4) Modify temp.il and change the declaration of GetCertificateProperty in base and derived classes to take an IntPtr for the last output parameter instead of a VARIANT *:

instance void
GetCertificateProperty([in] string marshal( bstr) strPropertyName,
[in] int32 PropertyType,
[out] native int pvarPropertyValue) runtime managed internalcall

5) Generate a new Interop.CertClientLib.dll which includes these modifications:

ilasm.exe /DLL temp.il /res:temp.res /out=Interop.CertClientLib.dll

6) Modify our project's references to use this new DLL.

7) Change our .NET code to use the IntPtr calling convention. Manage Variant objects from IntPtr and free the memory after its use. Sample:

<DllImport("Oleaut32.dll", PreserveSig:=False)> _
Private Shared Sub VariantClear(ByVal var As IntPtr)
End Sub

'Exit Module Notify callback
Public Sub Notify(ByVal ExitEvent As Integer, ByVal Context As Integer) Implements CERTEXITLib.ICertExit.Notify

Trace.WriteLine(">> Notify")

Dim oCSE As New CERTCLIENTLib.CCertServerExit
oCSE.SetContext(Context)

Trace.WriteLine(String.Format("ExitEvent = {0}", ExitEvent))
If ExitEvent = 1 Then 'Issue

' Allocate memory to hold VARIANT
'
Dim variantObjectPtr As IntPtr
variantObjectPtr = Marshal.AllocHGlobal(2048)

' Get VARIANT containing certificate bytes
'
oCSE.GetCertificateProperty("RawCertificate", DataTypeEnum.Binary, variantObjectPtr)

' Read ANSI BSTR information from the VARIANT as we know RawCertificate property
' is ANSI BSTR. Please note that the below code is written based on how the
' VARIANT structure looks like in C/C++
'
Dim bstrPtr As IntPtr
bstrPtr = Marshal.ReadIntPtr(variantObjectPtr, 8)

Dim bstrLen As Integer
bstrLen = Marshal.ReadInt32(bstrPtr, -4)

Dim certBytes(bstrLen - 1) As Byte
Marshal.Copy(bstrPtr, certBytes, 0, bstrLen)

Trace.WriteLine(String.Format("Certificate length = {0}", certBytes.Length))

' Clear the VARIANT which will free the memory allocated for the VARIANT members
'
VariantClear(variantObjectPtr)

' Get certificate
'
Dim x509 As New X509Certificate(certBytes)

' Store the certificate in the certificate store
'
Dim store As New StoreClass
Dim cert As New CertificateClass
Dim data As String = Convert.ToBase64String(x509.GetRawCertData)

cert.Import(data)
store.Open(CAPICOM_STORE_LOCATION.CAPICOM_LOCAL_MACHINE_STORE, _
"My", _
CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_READ_WRITE)
store.Add(cert)

' Get VARIANT containing Serial Number
'
oCSE.GetCertificateProperty("SerialNumber", DataTypeEnum.String, variantObjectPtr)

' Construct Variant .NET object from IntPtr
'
Dim variantObject As Object
variantObject = Marshal.GetObjectForNativeVariant(variantObjectPtr)

' Get Serial Number
'
Dim serialNo As String = variantObject

Trace.WriteLine(String.Format("Serial Number = {0}", serialNo))

' Free memory
'
VariantClear(variantObjectPtr)

' Get VARIANT containing Distinguished Name
'
oCSE.GetCertificateProperty("DistinguishedName", DataTypeEnum.String, variantObjectPtr)

' Construct Variant .NET object from IntPtr
'
variantObject = Marshal.GetObjectForNativeVariant(variantObjectPtr)

' Get Serial Number
'
Dim distinguishedName As String = variantObject

Trace.WriteLine(String.Format("DistinguishedName = {0}", distinguishedName))

' Free memory
'
VariantClear(variantObjectPtr)

' Get VARIANT containing Not Before date
'
oCSE.GetCertificateProperty("NotBefore", DataTypeEnum.Date, variantObjectPtr)

' Construct Variant .NET object from IntPtr
'
variantObject = Marshal.GetObjectForNativeVariant(variantObjectPtr)

' Get Not Before date
'
Dim validFrom As Date = variantObject

Trace.WriteLine(String.Format("Not Before = {0}", validFrom.ToString))

' Free memory
'
VariantClear(variantObjectPtr)

' Get VARIANT containing Not After date
'
oCSE.GetCertificateProperty("NotAfter", DataTypeEnum.Date, variantObjectPtr)

' Construct Variant .NET object from IntPtr
'
variantObject = Marshal.GetObjectForNativeVariant(variantObjectPtr)

' Get Not After date
'
Dim validTo As Date = variantObject

Trace.WriteLine(String.Format("Not After = {0}", validTo.ToString))

' Free memory
'
VariantClear(variantObjectPtr)

' Free the memory block allocated to hold VARIANT
'
Marshal.FreeHGlobal(variantObjectPtr)

End If

Trace.WriteLine("<< Notify")

End Sub


Note: Becareful when translating the sample to C#: "Dim certBytes(bstrLen - 1) As Byte" will translate to "byte[] rawCert = new byte[bstrLen];".



I've also seen this kind of issues when referencing CAPICOM.dll (Interop.CAPICOM.dll interop assembly). These errors may appear when we are i.e. decrypting a string:


System.Runtime.InteropServices.COMException: ASN1 unexpected end of data.
System.Runtime.InteropServices.COMException: ASN1 bad tag value met.


For instance, CAPICOM.Utilities.ByteArrayToBinaryString returns a BSTR that represents the binary data being passed as an input parameter. The interop layer assigns the BSTR to a .NET string which removes the last byte and makes the data even. By using an IntPtr as return type we can access the complete binary data as we've already seen. In this case we'll replace "string marhal(bstr)" to "native int" just before each ByteArrayToBinaryString occurrence in the IL file.



I hope this helps.


Cheers,



Alex (Alejandro Campos Magencio)



Credits: Sample code has been provided by my colleague Prabagar Ramadasse. Thank you Prabagar!

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/07/30/cryptographicexception-unable-to-open-the-access-token-of-the-current-thread/
Post name: CryptographicException: Unable to open the access token of the current thread
Original author: Alejandro Campos Magencio
Posting date: 2008-07-30T03:41:00+00:00


Hi all,


When working with RSACryptoServiceProvider, we may get an exception like the following:

System.Security.Cryptography.CryptographicException: Unable to open the access token of the current thread
at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
at System.Security.Cryptography.Utils._GetKeyParameter(SafeKeyHandle hKey, UInt32 paramID)
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)
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(CspParameters parameters)

I've seen this exception i.e. when signing an XML with a certificate associated to a specific Cryptographic Service Provider (CSP).


Let's use my CryptoAPI Tracer scriptto get all CryptoAPI calls that .NET is making behind the scenes. This will help us to know exactly what's going on.


We can see in the logs generated by the script the call to CryptAcquireContext we make to get the key container of the certificate. We canalso see the CSP associated to the certificate:

CryptAcquireContextA (0xd8c)

IN
pszContainer
00228620 "2e065b6c-027a-4746-bdf3-901a9980"
00228640 "66ea"

pszProvider
00205e60 "Third-party CSP"

dwProvType
PROV_RSA_FULL

dwFlags
0

OUT
hProv
0x228248

RESULT
CryptAcquireContextA (0xd8c) SUCCEEDED


Note: I won't use the real name of the third-party CSP where I detected this issue. This post is for illustration purposes only. I don't prettend to blame anyone in particular.


Then .NET makes the following call to CryptGetKeyParam to find out the size of a buffer which will hold the value of the algorithm of the key:

CryptGetKeyParam (0xd8c)

IN
hKey
0x230058

dwParam
KP_ALGID

pbData
NULL

dwDataLen
0

dwFlags
0

OUT
dwDataLen
4

RESULT
CryptGetKeyParam (0xd8c) FAILED
LastErrorValue: (HRESULT) 0x80010125 (2147549477) - Unable to open the access token of the current thread
LastStatusValue: (NTSTATUS) 0xc0000034 - Object Name not found.


Note on IN parameters:


- This hKey value is the key handle.


- This dwParam value indicates that we want the algorithm of the key.


- This pbData value indicates that there is no buffer available yet.


- This pdwDataLen value is 0 because we want to find out the size that we need for the pbData buffer.


- dwFlags should always be 0.


This call is returning 4 in dwDataLen, which is correct as the ALGID will be 4 bytes long. But the API is also returning the error 0x80010125 (2147549477) - "Unable to open the access token of the current thread". CryptoAPI just passes the parameters to the CSP and returns the results. Thecall to the API is 100% correct, but the CSP is not honoring it. This is not a bug in .NET or CryptoAPI. This is a bug on the CSP.


If you are facing this issue please contact the CSPprovider. At least inthe case of the third-party CSP where we found the issue they were already aware of it. They immediately sent an upgrade to our customers which fixed this issue.


I hope this helps.


Cheers,



Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/07/29/how-to-use-dheapmon-exe-to-troubleshoot-desktop-heap-issues/
Post name: How to use DHeapMon.exe to troubleshoot Desktop Heap issues
Original author: Alejandro Campos Magencio
Posting date: 2008-07-29T07:26:00+00:00


Hi all,

The other day I had to troubleshoot an issue with aprocess that a specific Windowsservice was trying to launch without success. We could see with a debugger (i.e. WinDbg) that the new process was actually being created but it exited before we even got to its main function.

This may be a typical Desktop Heap issue. I won't explain Desktop Heap here. We already have a great overview on this topic: Desktop Heap Overview. They also explain how to troubleshoot this kind of issues with DHeapMon tool. You should read this article before you continue reading this post.

If everything is explained in previous link, why do I write this post? Well, they don't explain in detail how to configure and use DHeapMon, and it took me a while to figure this out. I hope this saves you some time.

We can get latest version of DHeapMon.exe here: Desktop Heap Monitor Version 8.1.

You will also need to download the following set of tools: Debugging Tools for Windows. We'll need Symchk.exe tool included in those tools.

Once you installalltools in target machineyou can use cmd.exe to run the following commands:

1) Set Microsoft symbols server:

set _NT_SYMBOL_PATH = srv*c:\symbols*http://msdl.microsoft.com/download/symbols

Note: We'll need win32k.sys correct symbols to be able to setup DHeapMon.

2) Load symbols for win32k.sys:

symchk c:\windows\system32\win32k.sys /v

Note: The symbols we need will be copied to c:\symbols.

3) Install DHeapMon driver:

dheapinst -y c:\symbols

4) Start DHeapMon driver:

dheapmon.exe -l

5) Get DHeapMon output:

5.1) For current user session:

dheapmon.exe

5.2) For session 0 (Windows services):

at 13:12 c:\path_to_dheapmon\dheapmon.exe -f c:\result.txt

Note: To access session 0 information we need to run DHeapMon under a high priviledged account in the Windows services world. A trick to do that is to launch the tool as an scheduled task with an AT command. This wayDHeapMon will be running (by default) as System in the same session as the otherservices.We print the output to a .txt file because the tool will be running in an invisible desktop.

6) Stop DHeapMon driver:

dheapmon.exe -u

7) Uninstall DHeapMon driver:

dheapinst.exe -r

DHeapMon shows Desktop Heap usage for different Window Station\Desktop combinations. If you want to understand these combinations better, check this article: INFO: Services, Desktops, and Window Stations.

If you want to know more info on typical Desktop Heap issues and changes to SharedSection and SessionViewSize values (commented in the overview article above), check these articles:

184802 PRB: User32.dll or Kernel32.dll fails to initialize

840342 Applications may not run correctly in a Terminal Services environment

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/07/18/how-to-use-windbg-to-debug-a-dump-of-a-32bit-net-app-running-on-a-x64-machine/
Post name: How to use Windbg to debug a dump of a 32bit .NET app running on a x64 machine
Original author: Alejandro Campos Magencio
Posting date: 2008-07-18T04:26:00+00:00


Hi all,


Imagine we are running a 32bit .NET app in a x64 machine. This app is failing so we have taken a memory dump of the app when a specificCLR exception gets raised.


We open the dump with our 64bit version of Windbg (Windbg x64), and we verify that we actualy got the dump when the exception happened:

This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(27e8.2904): CLR exception - code e0434f4d (first/second chance not available)
wow64!Wow64NotifyDebugger+0x9:
00000000`78be6369 b001 mov al,1

We check the unmanaged call stack and it looks like this:

0:000> k
Child-SP RetAddr Call Site
00000000`0012e170 00000000`78be64f2 wow64!Wow64NotifyDebugger+0x9
00000000`0012e1a0 00000000`78be6866 wow64!Wow64KiRaiseException+0x172
00000000`0012e510 00000000`78b83c7d wow64!Wow64SystemServiceEx+0xd6
00000000`0012edd0 00000000`78be6a5a wow64cpu!ServiceNoTurbo+0x28
00000000`0012ee60 00000000`78be5e0d wow64!RunCpuSimulation+0xa
00000000`0012ee90 00000000`78ed8501 wow64!Wow64LdrpInitialize+0x2ed
00000000`0012f6c0 00000000`78ed6416 ntdll!LdrpInitializeProcess+0x17d9
00000000`0012f9d0 00000000`78ef3925 ntdll!_LdrpInitialize+0x18f
00000000`0012fab0 00000000`77d59640 ntdll!KiUserApcDispatch+0x15
00000000`0012ffa8 00000000`00000000 0x77d59640
00000000`0012ffb0 00000000`00000000 0x0
00000000`0012ffb8 00000000`00000000 0x0
00000000`0012ffc0 00000000`00000000 0x0
00000000`0012ffc8 00000000`00000000 0x0
00000000`0012ffd0 00000000`00000000 0x0
00000000`0012ffd8 00000000`00000000 0x0
00000000`0012ffe0 00000000`00000000 0x0
00000000`0012ffe8 00000000`00000000 0x0
00000000`0012fff0 00000000`00000000 0x0
00000000`0012fff8 00000000`00000000 0x0

Well, all this wow64 stuffdoesn't tell us much...This is the 64bit look of our 32bit app. Let's try to take a look to the managed call stack. We need to load SOS extension for that:

0:000> .loadby sos mscorwks
The call to LoadLibrary(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos) failed, Win32 error 0n193
"%1 is not a valid Win32 application."
Please check your debugger configuration and/or network access.

Well, this error makes sense. We are trying to load 32bit version of SOS into 64bit version of Windbg. 64bit version of SOS should be here: C:\Windows\Microsoft.NET\Framework64\v2.0.50727.


Let's try our 32bit version of Windbg (Windbg x86) then. We can alsosee the exception and the unmanaged call stack withall that wow64 stuff. Same as before. Let's try to load SOS then:

0:000> .loadby sos mscorwks

Ok, it seems to be loaded. Let's check it:

0:000> .chain
Extension DLL search Path:
...
Extension DLL chain:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos: image 2.0.50727.1434, API 1.0.0, built Thu Dec 06 05:42:38 2007
[path: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll]
...

Great. We are doing business. I don't want to see all that wow64 stuff, I want to see the managed (.NET) call stack with SOS:

0:000> !clrstack
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
2) the file mscordacwks.dll that matches your version of mscorwks.dll is
in the version directory
3) or, if you are debugging a dump file, verify that the file
mscordacwks___.dll is on your symbol path.
4) you are debugging on the same architecture as the dump file.
For example, an IA64 dump file must be debugged on an IA64
machine.

You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.

If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.


Weird.We got an unexpected error.Our debugger is the latest version, butit seems our version ofmscorwks.dllis different than the one in the machine where we got the dump. Mscordacwks.dll is different too, of course. But Windbg should find mscordacwks in my symbol path... Let's verify that the path is correct:

0:000> .sympath
Symbol search path is: srv*c:\symbolspub*http://msdl.microsoft.com/downloads/symbols

The symbol path is correctly pointing to our Microsoft public symbol server, that should be enough... I'm alsosupposed to be debugging in the same architecturebecause I took the dump in AMD64 and my machine isAMD64, too. Let's try that command they mention in the error:

0:000> .cordll -ve -u -l
CLRDLL: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.1434 f:0
doesn't match desired version 2.0.50727.832 f:0
CLRDLL: ERROR: DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll init failure, Win32 error 0n87
CLR DLL status: ERROR: DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll init failure, Win32 error 0n87

Interesting. We just confirmed the version we need of mscordacwks is different than the one we have, it seems we are getting the right mscordacwks version from the symbol server, but we can't initialize the dll.


Wait a sec! All that wow64 stuff we saw in the call stack implies that we are in the 64bit part of our 32bit application. Let's try to see 32bit stuff only:

0:000> .load wow64exts
0:000> !sw
Switched to 32bit mode

Let's check the unmanaged call stack:

0:000:x86> kL
ChildEBP RetAddr
002df57c 79f55b05 kernel32!RaiseException+0x53
002df5dc 7a0904d5 mscorwks!RaiseTheExceptionInternalOnly+0x226
002df6a0 79646e0c mscorwks!JIT_Throw+0xfc
002df700 79465607 mscorlib_ni!System.IO.__Error.WinIOError(Int32, System.String)+0x1e17f8
002df71c 79649b75 mscorlib_ni!System.IO.__Error.WinIOError()+0x1f
002df71c 06e1c8f7 mscorlib_ni!System.IO.Path.GetTempFileName()+0x1d9d5d
...

No wow64 stuff in there. Thisisa typical 32bitcall stack. We are good. Can we init mscordacwks.dll now?

0:000:x86> .cordll -ve -u -l
CLRDLL: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.1434 f:0
doesn't match desired version 2.0.50727.832 f:0
CLRDLL: Loaded DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll
CLR DLL status: Loaded DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll

It seems so! Let's check the managed call stack:

0:000:x86> !clrstack
OS Thread Id: 0x2904 (0)
ESP EIP
002df604 78be254a [HelperMethodFrame: 00000000002df604]
002df6a8 79646e0c System.IO.__Error.WinIOError(Int32, System.String)
002df708 79465607 System.IO.__Error.WinIOError()
002df710 79649b75 System.IO.Path.GetTempFileName()
...
002df8cc 7b074fef System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
002df8d0 7b082365 [InlinedCallFrame: 00000000002df8d0]
002df93c 7b147ff6 [InlinedCallFrame: 00000000002df93c]

Ok, now we are really doing business. Which exception did we get?

0:000:x86> !pe
Exception object: 000000000293a1cc
Exception type: System.IO.IOException
Message: The directory name is invalid.

InnerException:
StackTrace (generated):

StackTraceString:
HResult: 8007010b


We can now continue from here.


Do you want to know more on debugging wow64? Check these links:


Debugging WOW64


A look at the WoW64 layer in the debugger



I hope this helps.


Regards,



Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/06/30/threading-issues-with-rsacryptoserviceprovider/
Post name: Threading issues with RSACryptoServiceProvider
Original author: Alejandro Campos Magencio
Posting date: 2008-06-30T09:01:00+00:00


Hi all,

When using RSACryptoServiceProvider in i.e.ASP.NETyou 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".

We've seen in past posts (sample) how we can use my CryptoAPI Tracer scriptto take a look to the CryptoAPIcalls thatRSACryptoServiceProvider makes behind the scenes.

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 constructordoes similar to any application calling CryptAcquireContext APItwice.

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:

1) Set CspParameters.KeyContainer name when creating an RSACryptoServiceProvider object. Never use the default one, NEVER! This is prone to store corruption. We talked about this already.

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.Flagswhen 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.

Regards,

Alex (Alejandro Campos Magencio)