出于安全原因,我正在尝试清除C#字符串的内存内容。
我知道SecureString
课程,但遗憾的是我无法在我的申请中使用SecureString
代替String
。需要清除的字符串是在运行时动态创建的(例如,我不是要清除字符串文字)。
我发现的大多数搜索结果基本上都说清除String
的内容是不可能的(因为字符串是不可变的)并且应该使用SecureString
。
因此,我确实在下面提出了自己的解决方案(使用不安全的代码)。测试显示解决方案有效,但我仍然不确定解决方案是否有任何问题?还有更好的吗?
static unsafe bool clearString(string s, bool clearInternedString=false)
{
if (clearInternedString || string.IsInterned(s) == null)
{
fixed (char* c = s)
{
for (int i = 0; i < s.Length; i++)
c[i] = '\0';
}
return true;
}
return false;
}
编辑:由于GC上的注释会在调用clearString
之前移动字符串:以下代码段如何?
string s = new string('\0', len);
fixed (char* c = s)
{
// copy data from secure location to s
c[0] = ...;
c[1] = ...;
...
// do stuff with the string
// clear the string
for (int i = 0; i < s.Length; i++)
c[i] = '\0';
}
答案 0 :(得分:17)
你的问题是字符串可以移动。如果GC运行,它可以将内容移动到新位置,但它不会将旧内容清零。如果您确实将相关字符串清零,则无法保证其副本不会存在于内存中的其他位置。
这是.NET垃圾收集器的link,它讨论了压缩。
编辑: 这是您更新的问题:
// do stuff with the string
问题是,一旦它离开你的控制,你就失去了确保它安全的能力。如果它完全在您的控制之下,那么您将不会仅使用字符串类型的限制。简而言之,这个问题已存在很长时间了,没有人提出一种安全的方法来处理这个问题。如果您想保证其安全,最好通过其他方式处理。清除字符串意味着阻止某人通过内存转储找到它。如果您不能使用安全字符串,则阻止此操作的最佳方法是限制访问运行代码的计算机。
答案 1 :(得分:11)
除了标准&#34;你正在进入不安全的领域&#34;回答,我希望自己解释一下,考虑以下几点:
CLR并不保证在任何给定点只有一个字符串实例,并且它不保证字符串将被垃圾收集。如果我要做以下事情:
var input = "somestring";
input += "sensitive info";
//do something with input
clearString(input, false);
结果是什么? (让我们假设我没有使用字符串文字,而这些是来自某种某种环境的输入)
使用&#34; somestring&#34;的内容创建一个字符串。另一个字符串是使用&#34;敏感信息&#34;的内容创建的,而另一个字符串是使用&#34; somestringsensitive info&#34;的内容创建的。只清除后一个字符串:&#34;敏感信息&#34;不是。它可能会也可能不会立即被垃圾收集。
即使您要小心确保始终清除任何包含敏感信息的字符串,CLR仍然不能保证只存在一个字符串实例。
<强> 编辑: 强> 关于您的编辑,只需立即固定字符串可能会产生所需的效果 - 无需将字符串复制到其他位置或任何其他位置。您确实需要在收到所述字符串后立即执行此操作,并且还有其他安全问题需要担心。例如,您无法保证字符串的来源在ITS内存中没有它的副本,而没有清楚地了解源以及它是如何做到的。
由于显而易见的原因,你也无法改变这个字符串(除非变异的字符串与字符串完全相同),你需要非常小心,你所做的一切都不能踩到记忆不是该字符串的一部分。
此外,如果您将其传递给您自己没有写过的其他功能,则可能会或可能不会被该功能复制。
答案 2 :(得分:5)
在您尝试清除字符串之前,无法确定字符串经过多少次CLR和非CLR函数。这些函数(托管和非托管)可能由于各种原因(可能是多个副本)创建字符串的副本。
你不可能知道所有这些地方,并且如此逼真地清除它们,你不能保证你的密码从内存中清除。您应该使用SecureString
,但您需要了解上述内容仍然适用:在您的程序中的某个时刻,您将收到密码,并且您必须将其保存在内存中(甚至如果你将它移动到一个安全的字符串,只是持续一小段时间)。这意味着您的字符串仍将通过您无法控制的函数调用链。
答案 3 :(得分:0)
如果您真的无法使用SecureString
,并且您愿意编写不安全的代码,那么您可以编写自己的简单字符串类,该类使用非托管内存并确保在释放之前将所有内存归零
但是,您永远无法真正确保您的数据安全,因为您永远无法完全控制它。例如,嵌入的病毒足够深,可以在程序运行时读取该内存,这也是进程终止的可能性,在这种情况下,析构函数代码不会运行,将数据保留在未分配的内存中,可以分配给另一个进程,它最初仍然包含您的敏感数据;有人可以轻松地使用visual studio等工具来监控调试过程的内存,或编写一个分配内存并搜索敏感数据的程序。
答案 4 :(得分:0)
作为SecureString的用户,有时我会从常规字符串中获取输入,并用来将传入的字符串存储器固定为零,一旦将其放入SecureString中,就像您所做的一样。 然后,我遇到了一个奇怪的错误,其中一个第三方库(Redis)的内存被归零。事实证明,第三方库具有两个字符串实例,其内容与测试输入常规字符串(“密码”)完全相同。显然.NET优化了所有3个字符串以指向相同的内存缓冲区。因此,当我固定并归零字符串的“自己”的内存时,原来我也归零了第三方库的内存。然后,Redis客户端库无法解析连接字符串,并显示错误消息:“密码”不是可识别的密钥。 因此,我学到的困难方法是不要将一个字符串的内存归零,因为它也可能是另一个内容相同的字符串的内存。