【问题标题】:Clear C# String from memory从内存中清除 C# 字符串
【发布时间】:2015-11-22 05:38:32
【问题描述】:

出于安全原因,我正在尝试清除 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 上的 cmets 在调用 clearString 之前移动了字符串:那么下面的 sn-p 呢?

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

【问题讨论】:

  • 您在安全方面的尝试是错误的——您试图实现不可能的绝对安全。保护数据是为了让未经授权的人尽可能难以检索数据。如果有人可以直接访问您机器的内存,那么您需要担心更大的问题。使用SecureString,它会给你最好的结果。
  • 请注意,您可以使用GCHandle.Alloc(s, GCHandleType.Pinned) 进行长期固定。完成后不要忘记使用Free() 取消固定它。
  • 如果您不能使用SecureString,那么您应该说出原因。存在三种可能性:您可能认为无法使用它是错误的,现在答案可以告诉您原因。或者你可能是对的,但原因为可能的替代方案提供了线索,或者第三,你是对的,但原因无助于解决问题或提供替代方案。但不知道原因,谁也说不清。
  • 我强烈推荐使用SecureString。说真的,您正在尝试(并且失败)重写几乎与 SecureString 中的封面相同的代码。那么为什么要重新发明轮子呢?尤其是当它如此无聊的时候......
  • 人们往往会忘记,为了使用SecureString,我们必须将其转换为string。只要 GC 需要,string 就会一直存在于内存中,所以即使他使用的是SecureString,我也认为这个问题是有效的。

标签: c# string security memory securestring


【解决方案1】:

你的问题是字符串可以移动。如果 GC 运行,它可以将内容移动到新位置,但不会将旧位置归零。如果您确实将有问题的字符串清零,则无法保证它的副本不存在于内存中的其他地方。

这是 .NET 垃圾收集器的 link,它谈到了压缩。

编辑: 这是您的更新问题:

// do stuff with the string

问题在于,一旦它脱离您的控制,您就无法确保它是安全的。如果它完全在您的控制范围内,那么您将不会受到仅使用字符串类型的限制。简单地说,这个问题已经存在很长时间了,没有人想出一个安全的方法来处理这个问题。如果你想保证它的安全,最好通过其他方式来处理。清除字符串是为了防止有人通过内存转储找到它。如果您不能使用安全字符串,阻止这种情况的最佳方法是限制对运行代码的机器的访问。

【讨论】:

    【解决方案2】:

    除了标准的“你正在进入不安全的领域”答案(我希望自己能解释清楚)之外,请考虑以下几点:

    CLR 不保证在任何给定点上只有一个字符串实例,也不保证字符串会被垃圾回收。如果我要执行以下操作:

    var input = "somestring";
    input += "sensitive info";
    //do something with input
    clearString(input, false);
    

    这样做的结果是什么? (假设我没有使用字符串文字,而是来自某种环境的输入)

    使用“somestring”的内容创建一个字符串。另一个字符串是用“敏感信息”的内容创建的,另一个字符串是用“somestringsensitive info”的内容创建的。只有后一个字符串被清除:“敏感信息”不是。它可能会或可能不会立即被垃圾收集。

    即使您小心翼翼地确保始终清除任何包含敏感信息的字符串,CLR 仍然不能保证字符串的唯一实例存在。

    编辑: 关于您的编辑,只需立即固定字符串可能会产生预期的效果 - 无需将字符串复制到另一个位置或任何东西。您确实需要在收到所述字符串后立即执行此操作,并且还有其他安全问题需要担心。例如,您不能保证字符串的源在 ITS 内存中没有它的副本,除非清楚地了解源以及它是如何工作的。

    由于显而易见的原因,您也将无法更改此字符串(除非更改后的字符串与该字符串的大小完全相同),并且您需要非常小心,您所做的任何事情都不会占用内存不是该字符串的一部分。

    另外,如果你将它传递给不是你自己编写的其他函数,它可能会或可能不会被该函数复制。

    【讨论】:

      【解决方案3】:

      在你的字符串到达​​你试图清除它的函数之前,不可能知道你的字符串通过了多少个 CLR 和非 CLR 函数。这些函数(托管和非托管)可能会出于各种原因创建字符串的副本(可能是多个副本)。

      您不可能知道所有这些地方并如此真实地清除它们,您无法保证您的密码已从记忆中清除。您应该改用SecureString,但您需要了解上述内容仍然适用:在您的程序中的某个时刻,您将收到密码并且您必须将其保存在内存中(即使只是在您将其移动到安全字符串中的短时间内)。这意味着您的字符串仍将通过您无法控制的函数调用链。

      【讨论】:

        【解决方案4】:

        作为 SecureString 的用户,我有时会从常规字符串中获取输入,并在将传入的字符串内存放入 SecureString 后将其归零,就像您正在做的那样。 然后我遇到了一个奇怪的错误,其中来自 3rd 方库 (Redis) 的内存被归零了。结果是第 3 方库有两个字符串实例,其内容与测试输入的常规字符串(“密码”)完全相同。显然 .NET 优化了所有 3 个字符串以指向相同的内存缓冲区。因此,当我将字符串的“自己”内存固定并归零时,结果发现我也在归零第 3 方库内存。然后 Redis 客户端库无法解析连接字符串,错误为“密码”不是可识别的密钥。 所以我学到的教训是不要将一个字符串的内存归零,因为它也可能是另一个具有相同内容的字符串的内存。

        【讨论】:

          【解决方案5】:

          如果您真的无法使用SecureString,并且您愿意编写不安全的代码,那么您可以编写自己的简单字符串类,该类使用非托管内存并确保所有内存在释放前清零。

          但是,您永远无法真正确保您的数据是安全的,因为您永远无法完全控制它。例如,嵌入足够深的病毒可以在程序运行时读取该内存,这也有可能导致进程终止,在这种情况下,析构函数代码将无法运行,从而将数据留在未分配的内存中,这可能被分配给另一个进程,它最初仍会包含您的敏感数据;有人可以轻松地使用 Visual Studio 等工具来监控被调试进程的内存,或者编写分配内存并搜索敏感数据的程序。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-08-11
            • 1970-01-01
            • 2021-09-13
            • 2014-07-11
            • 1970-01-01
            相关资源
            最近更新 更多