0

I am trying to write some functions which allow encrypting/decrypting a chunk of data. For compatibility reasons, it needs to be symmetrical encryption which can be saved on one machine and read back on another, so that rules out the use of ProtectedData since that doesn't scope any wider than the local machine. (Too bad, just adding that option would have saved a lot of hassle.)

I've done a lot of web searching and so far I haven't come across any good examples that really attempt to make a clean transition from encrypted value on disk to SecureString in memory with no traces in between. I'd really like to see how (if?) it can be done.

I have an Encrypt function which seems to work reasonably well. You give it a SecureString, and it returns back a regular string containing an encrypted copy of the contents of the SecureString. If I save a memory dump after calling the function, I am unable to find any copies of my unencrypted data anywhere in the dump. THIS IS GOOD.

However I am not having as much luck with the Decrypt counterpart. While I can successfully decrypt my data and get it back in to a SecureString, I end up with multiple copies of the decrypted value in the memory dump by the time it is done. Of course having copies scattered about in clear text nullifies the whole point of using in memory cryptography. THIS IS BAD.

Can anyone make any suggestions on how to make this code "clean" so that no copies of the unencrypted value are left in memory after it completes?

Here is my code:

public SecureString Decrypt(string base64EncryptedText)
{
    using (SymmetricAlgorithm sa = GetAlgorithm())
    {
        ICryptoTransform transform = sa.CreateDecryptor();
        var result = new SecureString();
        using (var memstream = new MemoryStream())
        {
            using (var cs = new CryptoStream(memstream, transform, CryptoStreamMode.Write))
            {
                byte[] base64EncryptedTextByteArray = Convert.FromBase64String(base64EncryptedText);
                cs.Write(base64EncryptedTextByteArray, 0, base64EncryptedTextByteArray.Length);
                cs.FlushFinalBlock();  // If you don't do this, results are inconsistent from the ToArray method.

                byte[] decodedBytes = memstream.ToArray();

                // Clear the contents of the memory stream back to nulls
                memstream.Seek(0, 0);
                for (int i = 0; i < memstream.Length; i++)
                { memstream.WriteByte(0); }

                char[] decodedChars = Encoding.UTF8.GetChars(decodedBytes);

                // Null out the bytes we copied from the memory stream
                for (int i = 0; i < decodedBytes.Length; i++)
                { decodedBytes[i] = 0; }

                // Put the characters back in to the SecureString for safe keeping and null out the array as we go...
                for (int i = 0; i < decodedChars.Length; i++)
                {
                    result.AppendChar(decodedChars[i]);
                    decodedChars[i] = '\0';
                }
            }
        }
        return result;
    }
}

private SymmetricAlgorithm GetAlgorithm()
{
    string password = "DummyPassword";
    string salt = "salty";

    DeriveBytes rgb = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));
    SymmetricAlgorithm sa = new RijndaelManaged();
    sa.Key = rgb.GetBytes(sa.KeySize >> 3);
    sa.IV = rgb.GetBytes(sa.BlockSize >> 3);
    return sa;
}

If I trace through the code and make a memory dump (Debug | Save Dump As...), then search through the resulting file for my unencrypted value, I am clean all the way up to the cs.FlushFinalBlock() statement. As soon as that executes, I have 3 copies of my unencrypted value in the dump file.

I am guessing one of them is the memory stream buffer itself. I can eliminate that one with the Seek and WriteByte loop. A couple more copies pop up later with the decodedBytes and decodedChars arrays, but those also go away with the loops that null them out. That still leaves behind two of the copies created by the flush though.

I'm not shooting for no copies ever exist at any time in memory here. (Even though it would be ideal) I figure that is an impossibility without writing a WHOLE lot of code. My goal is simply to have no traces remain in memory after the function exits to keep the attack surface to a minimum.

For the sake of completeness, might as well include the Encrypt function too. Like I said, this one seems to work ok as it is. I don't find any remnants when it's done.

unsafe public string Encrypt(SecureString textToEncrypt)
{
    using (SymmetricAlgorithm sa = GetAlgorithm())
    {
        ICryptoTransform ict = sa.CreateEncryptor();
        using (MemoryStream memStream = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(memStream, ict, CryptoStreamMode.Write))
            {
                IntPtr unmanagedBytes = Marshal.SecureStringToGlobalAllocAnsi(textToEncrypt);
                try
                {
                    byte* bytePointer = (byte*)unmanagedBytes.ToPointer();
                    byte[] singleByte = new byte[1];
                    while (*bytePointer != 0) // This is a null terminated value, so copy until we get a null
                    {
                        singleByte[0] = *bytePointer;
                        cs.Write(singleByte, 0, 1);
                        singleByte[0] = 0;
                        bytePointer++;
                    }
                }
                finally
                {
                    Marshal.ZeroFreeGlobalAllocAnsi(unmanagedBytes);
                }
            }
            string base64EncryptedText = Convert.ToBase64String(memStream.ToArray());
            return base64EncryptedText;
        }
    }
}

The other thing I haven't explored that much yet is what happens if the garbage collector runs in the middle of all this... All bets are off if that happens, so would it be a good idea to run GC at the start of these functions to reduce the likelihood of it happening in the middle?

Thanks.

Steve In CO
  • 5,746
  • 2
  • 21
  • 32
  • If you want to be absolutely sure that clear-text and/or keys are never exposed in memory then you have to consider using hardware crypto modules (HSMs). Unfortunately this is expensive. – clausc Sep 17 '14 at 11:55
  • True, but I am doing this as much as a learning exercise as anything else. Can it be done in native .NET code? Obviously MS was thinking about the problem with the introduction of SecureString and all the methods which now accept them. Seems like they left some holes in the CryptoStream though. – Steve In CO Sep 17 '14 at 13:21

0 Answers0