185

我正在尝试了解 .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 的哪个方面?

4

11 回答 11

108

当前使用的框架的某些部分SecureString

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

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

SecureStrings 是解决鸡和蛋问题的第一步,因此即使大多数当前场景需要将它们转换回常规字符串才能完全使用它们,它们在框架中的存在现在意味着在未来 - 至少到您的程序不必成为薄弱环节的程度。

于 2008-09-26T19:20:38.990 回答
37

编辑不要使用 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 数组的内容是加密的。由于缺少 API 或密钥管理问题,.NET 并不支持所有环境中的加密。

推荐

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

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

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

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

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

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

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

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

附加信息:

  • 来自 .NET Security 博客的帖子与此处介绍的内容大致相同。
  • 另一个 重新审视它并提到了一个可以转储 SecureString 内容的工具。

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

于 2008-09-27T11:48:56.850 回答
20

简答

为什么我不能说:

SecureString password = new SecureString("password");

因为现在你password在记忆中;无法擦除它 - 这正是SecureString的重点。

长答案

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

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

用零填充一块内存。

注意: SecureZeroMemory 与 相同ZeroMemory只是编译器不会对其进行优化。

问题是您不能在.NET 内部调用ZeroMemory或调用。SecureZeroMemory而在 .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 属性

获取 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 中?那么你需要做的是首先停止在字符串中使用密码。你需要把它放在别的东西里。即使是Char[]数组也会有所帮助。

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

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

您需要将密码存储在可以擦除的内存中从那里将其加载到 SecureString 中。


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

有些人看不到在设备被锁定时从内存中擦除用户密码在验证后从内存中擦除击键的意义。那些人不使用 SecureString。

于 2016-12-16T21:17:41.370 回答
15

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

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

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

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

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

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

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

如果没有上述内容,SecureString 的价值将有限。

于 2008-09-26T19:02:03.043 回答
12

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

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

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

于 2008-09-26T18:47:52.063 回答
11

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

于 2008-09-26T18:49:13.977 回答
5

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

于 2008-09-26T18:55:32.217 回答
4

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

于 2008-09-26T18:46:09.930 回答
4

好吧,正如描述所述,该值是加密存储的,这意味着您的进程的内存转储不会显示字符串的值(没有一些相当严肃的工作)。

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

于 2008-09-26T18:51:33.693 回答
4

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

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

于 2016-07-21T00:12:10.863 回答
1

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

于 2017-10-31T03:30:57.747 回答