解密后如何避免内存中未加密的数据副本?

时间:2014-09-17 03:08:02

标签: c# encryption persistence securestring

我正在尝试编写一些允许加密/解密大量数据的函数。出于兼容性原因,它需要是对称加密,可以保存在一台机器上并在另一台机器上回读,因此排除了ProtectedData的使用,因为它的范围不比本地机器宽。 (太糟糕了,只需添加该选项就可以省去很多麻烦。)

我已经进行了大量的网络搜索,到目前为止,我还没有遇到过任何真正尝试从磁盘上的加密值到内存中的SecureString的干净过渡的好例子,两者之间没有任何痕迹。我真的很想知道如何(如果?)可以做到。

我有一个加密功能,似乎运行得相当好。你给它一个SecureString,它返回一个包含SecureString内容的加密副本的常规字符串。如果我在调用函数后保存内存转储,我无法在转储中的任何位置找到任何未加密数据的副本。这很好。

然而,我对Decrypt对手没有那么多运气。虽然我可以成功解密我的数据并将其恢复到SecureString,但是当它完成时,我最终会在内存转储中找到解密值的多个副本。当然,以明文分散的副本会使内存加密的整个使用点无效。这很糟糕。

有人可以就如何使这段代码“干净”提出任何建议,以便在完成后没有未加密的值的副本留在内存中吗?

这是我的代码:

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;
}

如果我浏览代码并进行内存转储(Debug | Save Dump As ...),那么在生成的文件中搜索我的未加密值,我一直都很干净,直到cs.FlushFinalBlock()声明。执行后,我在转储文件中有3份未加密的值。

我猜其中一个是内存流缓冲区本身。我可以使用Seek和WriteByte循环消除那个。稍后会使用decodeBytes和decodingChars数组弹出几个副本,但是这些副本也会消除掉它们的循环。这仍然留下了由flush创建的两个副本。

我不是因为这里没有任何副本存在于内存中。 (即使它是理想的)我认为这是不可能的,没有编写全部代码。我的目标只是在函数退出后没有痕迹留在内存中以使攻击面保持最小。

为了完整起见,也可以包括加密功能。就像我说的那样,这个似乎工作正常。完成后我没有找到任何残余物。

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;
        }
    }
}

我还没有探索过的另一件事是,如果垃圾收集器在所有这些中间运行会发生什么......如果发生这种情况,所有的赌注都会关闭,所以在以下位置运行GC是个好主意这些函数的开始是为了减少它在中间发生的可能性吗?

感谢。

0 个答案:

没有答案