15

I have a high-level goal of creating a static utility class that encapsulates the encryption for my .NET application. Inside I'd like to minimize the object creations that aren't necessary.

My question is: what is the thread-safety of the classes which implement symmetric encryption within the .NET Framework? Specifically System.Security.Cryptography.RijndaelManaged and the ICryptoTransform types it generates.

For instance, in my class constructor can I simply do something along the following lines?

static MyUtility()
{
    using (RijndaelManaged rm = new RijndaelManaged())
    {
        MyUtility.EncryptorTransform = rm.CreateEncryptor(MyUtility.MyKey, MyUtility.MyIV);
        MyUtility.DecryptorTransform = rm.CreateDecryptor(MyUtility.MyKey, MyUtility.MyIV);
    }
}

Side-stepping the issue of is it secure to have Key and IV exist within this class, this example block brings up a number of other questions:

  1. Can I continually reuse the EncryptorTransform and DecryptorTransform over and over? The *.CanReuseTransform and *.CanTransformMultipleBlocks properties imply "yes", but are there any caveats I should be aware of?

  2. Since RijndaelManaged implements IDisposable my inclination is to put it within a using block especially since it probably ties into external OS-level libs. Are there any caveats with this since I'm keeping the ICryptoTransform objects around?

  3. Potentially the most important question, in a highly multithreaded environment, will I run into issues with sharing the ICryptoTransform objects between threads?

  4. If the answer to #3 is that it isn't thread-safe, will I experience serious performance degradation from locking while I use the ICryptoTransform objects? (Depends on load I suppose.)

  5. Would it be more performant to simply instantiate new RijndaelManaged each time? Or store one RijndaelManaged and generate new RijndaelManaged().CreateEncryptor(...) each time?

I am hoping that someone out there knows how these work under the hood or are experienced with issues from similar implementations. I've found that a lot of these kinds of performance and thread-related issues typically do not manifest themselves until there is a sizable amount of load.

Thanks!

Piotr Dobrogost
  • 38,049
  • 34
  • 218
  • 341
mckamey
  • 16,875
  • 15
  • 76
  • 114

3 Answers3

17

1) Yes.

2) One you dispose of it, you cannot use it. Up until then, you can share/use it (but see below)

3-4) From MSDN:

"Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe. "

If you want to keep this around, and share it between threads, you'll need to implement locking and treat it as a locked resource. Otherwise, I'd recommend just making separate versions as needed, and disposing of them when you're done.

5) I would recommend creating these as needed, and then trying to optimize it if later you find you have a performance problem. Don't worry about the performance implications of creating a new version until you see that's its a problem after profiling.

Reed Copsey
  • 522,342
  • 70
  • 1,092
  • 1,340
  • Good sound advice. I guess I made the assumption that creating and destroying some of these objects came at a cost due to the underlying dependencies upon the navtive Platform SDK classes. If no one thinks this is a warranted concern, then it sounds like the synchronization mechanisms might in the end be the bigger bottleneck. – mckamey Jun 12 '09 at 18:50
  • 2
    True that you should not spend time optimizing prematurely, but just want to drop here that in our case we found that performing ~500 small encryptions (all with the same password) took almost 10 seconds which was a disaster. Nearly all the time was spent creating encryptors. Re-using ICryptoTransforms was the obvious solution. – Bernard May 04 '17 at 18:03
  • My 2cents, I found that _not_ reusing the ICryptoTransform caused very bad performance of our application. – gorillapower Nov 09 '17 at 06:20
4

One could solve the concurrency problem simply with a cache based on a concurrent stack:

static ConcurrentStack<ICryptoTransform> decryptors = new ConcurrentStack<ICryptoTransform>();

void Encrypt()
{
   // Pop decryptor from cache...
   ICryptoTransform decryptor;
   if (!decryptors.TryPop(out decryptor))
   {
       // ... or create a new one since cache is depleted
       AesManaged aes = new AesManaged();
       aes.Key = key;
       aes.IV = iv;
       decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    }

    try
    {
       //// use decryptor
    }
    finally
    {
       decryptors.Push(decryptor);
    }
 }
Andreas
  • 41
  • 1
0
  1. Absolutely NOT, I had troubles. I had been using it for two years when all of a sudden some of my most important code started throwing errors when decrypting. Result of Microsoft patches? McAfee? dot net framework updates? (framework 4.7.2)

When I finally figured it out I moved the CreateEncryptor and CreateDecryptor into a using inside the actual call to encrypt and decrypt. Problem fixed.

SteakOverflow
  • 4,517
  • 1
  • 30
  • 49