【问题标题】:When would I need a SecureString in .NET?我什么时候需要 .NET 中的 SecureString?
【发布时间】:2010-09-13 13:29:03
【问题描述】:

我正在尝试了解 .NET 的 SecureString 的用途。来自 MSDN:

System.String 类的实例是不可变的,并且在不再需要时,不能以编程方式安排垃圾回收;也就是说,实例在创建后是只读的,无法预测实例何时会从计算机内存中删除。因此,如果 String 对象包含密码、信用卡号或个人数据等敏感信息,则存在使用该信息后泄露的风险,因为您的应用程序无法从计算机内存中删除数据。

SecureString 对象与 String 对象的相似之处在于它具有文本值。但是,SecureString 对象的值是自动加密的,可以修改,直到您的应用程序将其标记为只读,并且可以由您的应用程序或 .NET Framework 垃圾收集器从计算机内存中删除。

SecureString 实例的值在实例初始化或修改值时自动加密。您的应用程序可以通过调用 MakeReadOnly 方法使实例不可变并防止进一步修改。

自动加密是大回报吗?

为什么我不能说:

SecureString password = new SecureString("password");

而不是

SecureString pass = new SecureString();
foreach (char c in "password".ToCharArray())
    pass.AppendChar(c);

我缺少 SecureString 的哪个方面?

【问题讨论】:

标签: .net security encryption


【解决方案1】:

目前使用SecureString的框架部分部分:

主要目的是减少攻击面,而不是消除它。 SecureStrings 被“固定”在 RAM 中,因此垃圾收集器不会移动它或复制它。它还确保纯文本不会被写入交换文件或核心转储中。加密更像是混淆,但不会阻止坚定的黑客,他们将能够找到用于加密和解密它的symmetric key

正如其他人所说,您必须逐个字符地创建 SecureString 的原因是因为这样做的第一个明显缺陷是:您可能已经将秘密值作为纯字符串,那么重点是什么?

SecureStrings 是解决鸡与蛋问题的第一步,因此即使大多数当前场景需要将它们转换回常规字符串以完全使用它们,它们在框架中的存在现在意味着将来对它们提供更好的支持 - 至少在某种程度上您的程序不必成为薄弱环节。

【讨论】:

  • 我在查看 ProcessStartInfo 的 Password 属性时遇到了它;甚至没有注意类型,我只是将它设置为常规字符串,直到编译器对我咆哮。
  • 要找到对称加密密钥并不容易,因为 SecureString 是基于 DPAPI 的,它不完全以明文形式存储密钥...
  • 另外,这不是鸡和蛋的问题,因为它不是存储加密的替代品 - 但它是不可变的托管 .NET 字符串的解决方法。
  • “你大概已经拥有了一个纯字符串的秘密值,那有什么意义呢?”这个问题有答案吗?如果您打算将密码长时间存储在内存中,这似乎是一个“聊胜于无”的解决方案。
  • 有几个简单的代码使用示例怎么样?我相信我会更好地了解如何以及何时使用它。
【解决方案2】:

编辑不要使用 SecureString

目前的指导方针现在说不应使用该类。详情可以在这个链接找到:https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

来自文章:

DE0001:不应使用 SecureString

动机

  • SecureString 的目的是避免在进程中存储秘密 内存为纯文本。
  • 但是,即使在 Windows 上,SecureString 也不作为操作系统概念存在。
    • 它只是使纯文本的窗口更短;它不完全 防止它,因为 .NET 仍然必须将字符串转换为纯文本 代表。
    • 好处是纯文本表示不会到处乱跑 作为System.String 的一个实例——本机缓冲区的生命周期是 更短。
  • 除了在 .NET Framework 上之外,数组的内容是未加密的。
    • 在 .NET Framework 中,内部 char 数组的内容是加密的。 .NET 也不支持所有环境中的加密 由于缺少 API 或密钥管理问题。

推荐

不要将SecureString 用于新代码。将代码移植到 .NET Core 时,请考虑 数组的内容没有在内存中加密。

处理凭据的一般方法是避免使用它们,而是 依靠其他方式进行身份验证,例如证书或 Windows 身份验证。

结束编辑:下面的原始摘要

很多很棒的答案;以下是所讨论内容的简要概要。

Microsoft 已实现 SecureString 类,旨在为敏感信息(如信用卡、密码等)提供更好的安全性。它会自动提供:

  • 加密(在内存转储的情况下 或页面缓存)
  • 固定在内存中
  • 能够标记为只读(以防止任何进一步的修改)
  • 安全构造,不允许传入常量字符串

目前,SecureString 的使用受到限制,但预计未来会得到更好的采用。

基于此信息,SecureString 的构造函数不应只获取一个字符串并将其分割为 char 数组,因为将字符串拼写出来会破坏 SecureString 的目的。

附加信息:

  • 来自 .NET 安全的post 博客讲的差不多 覆盖在这里。
  • 还有一个one 重新审视它并提到一个工具 可以转储的内容 安全字符串。

编辑:我发现很难选择最佳答案,因为很多信息都很好;太糟糕了,没有辅助答案选项。

【讨论】:

    【解决方案3】:

    简答

    为什么我不能说:

    SecureString password = new SecureString("password");
    

    因为现在你的内存中有password;无法擦除 - 这正是 SecureString 的重点。

    长答案

    SecureString 存在的原因是您在完成后无法使用 ZeroMemory 擦除敏感数据。它的存在是为了解决因为 CLR 存在的问题。

    在常规的 native 应用程序中,您会调用 SecureZeroMemory

    用零填充一块内存。

    注意:SecureZeroMemory 等同于ZeroMemoryexcept the compiler won't optimize it away.

    问题是您不能在 .NET 中调用 ZeroMemorySecureZeroMemory。而在 .NET 中,字符串是不可变的;你甚至不能像在其他语言中那样覆盖字符串的内容:

    //Wipe out the password
    for (int i=0; i<password.Length; i++)
       password[i] = \0;
    

    那你能做什么?我们如何在 .NET 中提供从内存中擦除密码或信用卡号的功能?

    唯一可行的方法是将字符串放在某个本机内存块中,您可以在其中调用ZeroMemory。本机内存对象,例如:

    • BSTR
    • 一个HGLOBAL
    • CoTaskMem 非托管内存

    SecureString 恢复失去的能力

    在 .NET 中,字符串在完成后无法擦除:

    • 它们是不可变的;你不能覆盖他们的内容
    • 你不能Dispose他们
    • 他们的清理工作由垃圾收集器支配

    SecureString 的存在是一种传递字符串安全的方法,并且能够在需要时保证它们的清理。

    你问了这个问题:

    为什么我不能说:

    SecureString password = new SecureString("password");
    

    因为现在你的内存中有password;没有办法擦掉它。它一直停留在那里,直到 CLR 碰巧决定重新使用该内存。你让我们回到了起点;一个正在运行的应用程序,其密码我们无法摆脱,并且内存转储(或进程监视器)可以在其中看到密码。

    SecureString 使用数据保护 API 将加密的字符串存储在内存中;这样,该字符串将不会存在于交换文件、故障转储中,甚至不会出现在本地变量窗口中,而同事正在查看您的内容。

    如何读取密码?

    然后是问题:我如何与字符串交互?你绝对想要这样的方法:

    String connectionString = secureConnectionString.ToString()
    

    因为现在您又回到了起点 - 一个您无法摆脱的密码。您希望强制开发人员正确处理敏感字符串 - 以便 从内存中擦除它。

    这就是为什么 .NET 提供了三个方便的帮助函数来将 SecureString 编组到非托管内存中:

    您将字符串转换为非托管内存 blob,处理它,然后再次擦除它。

    一些 API 接受 SecureStrings。例如,在 ADO.net 4.5 中,SqlConnection.Credential 采用一组 SqlCredential

    SqlCredential cred = new SqlCredential(userid, password); //password is SecureString
    SqlConnection conn = new SqlConnection(connectionString);
    conn.Credential = cred;
    conn.Open();
    

    您还可以在连接字符串中更改密码:

    SqlConnection.ChangePassword(connectionString, cred, newPassword);
    

    而且 .NET 中有很多地方出于兼容性目的继续接受纯字符串,然后迅速转身将其放入 SecureString。

    如何将文本放入 SecureString?

    这仍然存在问题:

    我首先如何将密码输入 SecureString?

    这是一个挑战,但重点是让您考虑安全性。

    有时已经为您提供了该功能。例如,WPF PasswordBox 控件可以将输入的密码作为 SecureString 直接返回给您:

    PasswordBox.SecurePassword Property

    获取PasswordBox当前持有的密码为SecureString

    这很有帮助,因为在您过去传递原始字符串的任何地方,您现在都有类型系统抱怨 SecureString 与 String 不兼容。在将 SecureString 转换回常规字符串之前,您希望尽可能长。

    转换 SecureString 很简单:

    • SecureStringToBSTR
    • PtrToStringBSTR

    如:

    private static string CreateString(SecureString secureString)
    {
        IntPtr intPtr = IntPtr.Zero;
        if (secureString == null || secureString.Length == 0)
        {
            return string.Empty;
        }
        string result;
        try
        {
            intPtr = Marshal.SecureStringToBSTR(secureString);
            result = Marshal.PtrToStringBSTR(intPtr);
        }
        finally
        {
            if (intPtr != IntPtr.Zero)
            {
                Marshal.ZeroFreeBSTR(intPtr);
            }
        }
        return result;
    }
    

    他们只是真的不想让你这样做。

    但是如何将字符串放入 SecureString?那么你需要做的是首先停止在 String 中使用密码。你需要把它放在 something else 中。即使是 Char[] 数组也会有所帮助。

    这时您可以附加每个字符在完成后擦除明文:

    for (int i=0; i < PasswordArray.Length; i++)
    {
       password.AppendChar(PasswordArray[i]);
       PasswordArray[i] = (Char)0;
    }
    

    您需要将密码存储在一些内存中,以便您擦除。从那里将其加载到 SecureString 中。


    tl;dr:SecureString 的存在是为了提供 ZeroMemory 的等效项。

    有些人在wiping the user's password from memory when a device is locked 中看不到重点,或者在抹去wiping keystrokes from memory after they'authenticated。那些人不使用 SecureString。

    【讨论】:

      【解决方案4】:

      在当前版本的框架中,您可以明智地使用 SecureString 的场景非常少。它实际上只对与非托管 API 交互有用 - 您可以使用 Marshal.SecureStringToGlobalAllocUnicode 对其进行编组。

      只要将它转换为 System.String 或从 System.String 转换,您就违背了它的目的。

      MSDN sample 从控制台输入一次生成一个字符的 SecureString,并将安全字符串传递给非托管 API。这是相当复杂和不切实际的。

      您可能期望 .NET 的未来版本对 SecureString 有更多支持,这将使其更有用,例如:

      • SecureString Console.ReadLineSecure() 或类似方法将控制台输入读入 SecureString,而无需示例中的所有复杂代码。

      • WinForms TextBox 替换,将其 TextBox.Text 属性存储为安全字符串,以便可以安全地输入密码。

      • 安全相关 API 的扩展,允许密码作为 SecureString 传递。

      没有上述内容,SecureString 的价值将受到限制。

      【讨论】:

        【解决方案5】:

        我相信您必须进行字符附加而不是一个平面实例化的原因是因为在后台将“密码”传递给 SecureString 的构造函数会将“密码”字符串放入内存中,从而破坏了安全字符串的目的。

        通过附加,您一次只将一个字符放入内存中,这可能不会在物理上彼此相邻,这使得重建原始字符串变得更加困难。我在这里可能是错的,但这就是向我解释的方式。

        该类的目的是防止安全数据通过内存转储或类似工具暴露。

        【讨论】:

          【解决方案6】:

          MS 发现,在某些导致服务器(台式机等)崩溃的情况下,运行时环境有时会执行内存转储,从而暴露内存中的内容。安全字符串在内存中对其进行加密,以防止攻击者能够检索到字符串的内容。

          【讨论】:

            【解决方案7】:

            SecureString 的一大好处是它应该避免由于页面缓存而将数据存储到磁盘的可能性。如果您在内存中有密码,然后加载大型程序或数据集,则您的密码可能会在您的程序被内存分页时写入交换文件。使用 SecureString,至少数据不会以明文形式无限期地保存在磁盘上。

            【讨论】:

              【解决方案8】:

              我会停止使用 SecureString 。看起来PG家伙正在放弃对它的支持。将来甚至可能会拉它 - https://github.com/dotnet/apireviews/tree/master/2015-07-14-securestring

              我们应该在 .NET Core 的所有平台上从 SecureString 中删除加密 - 我们应该废弃 SecureString - 我们可能不应该在 .NET Core 中公开 SecureString

              【讨论】:

              【解决方案9】:

              我猜这是因为该字符串是安全的,即黑客不应该能够读取它。如果你用字符串初始化它,黑客就可以读取原始字符串。

              【讨论】:

                【解决方案10】:

                好吧,正如描述所述,该值是加密存储的,这意味着您的进程的内存转储不会泄露字符串的值(无需进行一些相当认真的工作)。

                您不能仅从常量字符串构造 SecureString 的原因是,您在内存中拥有该字符串的未加密版本。将您限制为分段创建字符串可以降低将整个字符串一次存储在内存中的风险。

                【讨论】:

                • 如果他们限制从常量字符串的构造,那么 foreach (char c in "password".ToCharArray()) 行会打败它,不是吗?应该是 pass.AppendChar('p'); pass.AppendChar('a');etc?
                • 是的,您可以轻松地丢弃 SecureString 为您提供的小保护。他们试图让自己很难完全射中自己的脚。显然,必须有某种方法可以将值传入和传出 SecureString,否则您无法将其用于任何事情。
                【解决方案11】:

                另一个用例是当您使用支付应用程序 (POS) 时,您只是不能使用不可变数据结构来存储敏感数据,因为您是细心的开发人员。例如:如果我将敏感的卡数据或授权元数据存储到不可变字符串中,那么总会出现这种情况,即这些数据在被丢弃后会在内存中保留很长时间。我不能简单地覆盖它。将此类敏感数据加密保存在内存中的另一个巨大优势。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-09-21
                  • 2018-02-25
                  • 1970-01-01
                  • 2012-09-16
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多