我正在尝试编写一些允许加密/解密数据块的函数。出于兼容性原因,它需要是对称加密,可以保存在一台机器上并在另一台机器上读回,这样就排除了 ProtectedData 的使用,因为它的范围不会比本地机器更宽。(太糟糕了,只需添加该选项就可以省去很多麻烦。)
我已经进行了很多网络搜索,到目前为止,我还没有遇到任何真正尝试从磁盘上的加密值干净地过渡到内存中的 SecureString 的好例子,而两者之间没有任何痕迹。我真的很想看看如何(如果?)它可以完成。
我有一个 Encrypt 功能,它似乎工作得相当好。你给它一个 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;
}
如果我跟踪代码并进行内存转储(调试 | 将转储另存为...),然后在结果文件中搜索我的未加密值,我会一直清理到 cs.FlushFinalBlock() 语句。一旦执行,我在转储文件中有 3 个未加密值的副本。
我猜其中之一是内存流缓冲区本身。我可以用 Seek 和 WriteByte 循环消除那个。稍后会弹出更多副本,其中包含 decodedBytes 和 decodedChars 数组,但这些副本也会随着使它们无效的循环而消失。尽管如此,这仍然留下了由同花顺创建的两个副本。
我不是为了在记忆中的任何时候都没有副本而拍摄。(尽管这将是理想的)我认为如果不编写大量代码,这是不可能的。我的目标只是在函数退出后没有任何痕迹留在内存中,以将攻击面保持在最低限度。
为了完整起见,不妨也包括加密功能。就像我说的那样,这个似乎可以正常工作。完成后我没有发现任何残留物。
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;
}
}
}
我还没有深入探讨的另一件事是,如果垃圾收集器在这一切的中间运行会发生什么...这些功能可以减少它在中间发生的可能性吗?
谢谢。