RC2 Performance Enhancements

Posted by nhughes Mon, 13 Jun 2005 10:26:00 GMT

Since our app is still experiencing a severe bottle-neck with encryption, I requested some help from Microsoft to suggest improvements. While they had no suggestions for our problems with RSA efficiency, they did have two suggested changes to our RC2 implementation.

  1. I originally implemented our RC2 processing to generate a new session key for each data message sent to the client. They suggested, instead, that I reuse the session key throughout the life of the session because calling the RC2CryptoServiceProvider constructor is an expensive operation. (The RC2 session key is encrypted with RSA, so that will ensure its security.) NOTE: Encryption with RC2 is not thread safe, so you will have to lock your encryption/decryption code if you are going to use the same key through the session.
  2. I was using a CryptoStream to get encrypted and decrypted data from RC2, like this:

Decrypt Code:

lock (this)
{
    // Note:  The sessionKey is a byte[] read from the client.
    // The iv is a byte[]  of 0x00's to comply with the 
    // Microsoft Crypto Service Provider specs.

    ICryptoTransform decryptor 
        = rc2Decryptor.CreateDecryptor(sessionKey, iv);

    // Decrypt the data
    MemoryStream msDecrypt 
        = new MemoryStream(encryptedData.GetSegment());

    CryptoStream csDecrypt 
        = new CryptoStream(msDecrypt, decryptor, 
                           CryptoStreamMode.Read);

    int dataLen = encryptedData.Count;

    byte[] decryptedData = new byte[dataLen];

    //Read the data out of the crypto stream.
    int bytesRead = csDecrypt.Read(decryptedData, 0, dataLen);
}

Encrypt Code:

MemoryStream msEncrypt = new MemoryStream();

lock (this)
{
    // Encrypt the data.
    // Note:  encryptor was initialized earlier.
    // It is an ICryptoTransform that holds our session
    // key, which is held for the duration of the session.
    CryptoStream csEncrypt 
        = new CryptoStream(msEncrypt, encryptor, 
                           CryptoStreamMode.Write);

    //Write all data to the crypto stream and flush it.
    csEncrypt.Write(data.GetSegment(), data.Offset, data.Count);
    csEncrypt.FlushFinalBlock();
}

However, our team found that the streams were causing efficiency problems. So, Microsoft's suggestion was to use the TransportFinalBlock method on the ICryptoTransform interface and get rid of the Memory and Crypto streams. They claim it is a more efficient use of memory. So, the code now looks like this:

Decrypt Code:

byte[] decryptedData;

lock (this)
{
    ICryptoTransform decryptor 
        = rc2Decryptor.CreateDecryptor(sessionKey, iv);

    // Decrypt the data
    decryptedData 
        = decryptor.TransformFinalBlock(encryptedData.Array,
            encryptedData.Offset, encryptedData.Count);
}

Encrypt Code:

byte [] encryptedData;

lock (this)
{
    //Encrypt the data.
    encryptedData 
        = encryptor.TransformFinalBlock(data.Array, 
            data.Offset, data.Count);
}

We have implemented both of these suggestions are beginning our load test and profiling again to determine if they made a difference. I'll update the blog with the results when we have them.

By the way, in case you are curious about RSA... Microsoft stated that RSA encryption is slower pre-Windows XP because direct RSA encryption was not supported in the Crypto API. Unfortuanately, that doesn't explain our RSA problems, since we are on Windows 2003 server.

Comments

  1. Scott Hughes said 72 days later:
    In general, I'd recommend against synchronizing with "lock(this)". The reason is that the object this method is encapsulated by could be used by external methods for locking, thus starving your encryption method. It's good form to create a private internal object (I usually call mine lockObject, with something more descriptive used only if I need more than one) and synchronize only on this internal object. That way, your object itself will have total control over it's lock. It would be a nasty unintended side-effect if a consumer of an object discovered by chance that his call to your encryption method was blocking forever...

Comments are disabled