什么时候需要.NET中的SecureString?


179

我试图弄清.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的哪个方面?


3
11年后,MS不再建议SecureString进行新开发:github.com/dotnet/platform-compat/blob/master/docs/DE0001.md
Matt Thomas

Answers:


4

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

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


1
链接已死,但似乎可以继续运行:docs.microsoft.com/en-us/dotnet/api/… ..关于路径转发execpt“不使用凭据”的指导非常薄弱-github.com/dotnet/platform- compat / blob / master / docs / DE0001.md ..您也不敢使用密码来保护证书的私钥!
felickz

1
11年后,这个答案现在看来是“新的”正确答案。链接似乎过时了,但是来自MS的指导是:不应使用SecureString
Richard Morgan

109

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

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

正如其他人所说,之所以必须创建每个SecureString字符,是因为这样做的第一个明显缺陷是:您大概已经拥有秘密值作为纯字符串,那么,这又意味着什么呢?

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


2
我查看了ProcessStartInfo的Password属性,发现了它;甚至不注意类型,我只是将其设置为常规字符串,直到编译器对我咆哮。
理查德·摩根

很难找到对称的加密密钥,因为SecureString基于DPAPI,它不会以纯文本形式完全存储密钥...
AviD

1
而且,它并没有太大的麻烦,因为它并不是存储加密的替代品,而是解决不可变,托管的.NET字符串的一种解决方法。
AviD

2
“您大概已经拥有秘密值作为纯字符串,那又有什么意义呢?” 这个问题有答案吗?如果您打算将密码长时间保存在内存中,那么充其量似乎是一个“比没有好”的解决方案。
xr280xr

2
如果有几个简单的用法示例代码呢?我相信我会更好地了解如何以及何时使用它。
Codea

37

编辑不要使用SecureString

现在,当前的指导意见表明不应使用该类。可以在以下链接中找到详细信息:https : //github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

从文章:

DE0001:不应使用SecureString

动机

  • 目的SecureString是避免将秘密作为纯文本存储在过程存储器中。
  • 但是,即使在Windows上,SecureString也不存在OS概念。
    • 它只是使获取纯文本的窗口更短;它并不能完全阻止它,因为.NET仍然必须将字符串转换为纯文本表示形式。
    • 好处是纯文本表示不会作为的实例而徘徊System.String-本机缓冲区的生存期更短。
  • 除了.NET Framework以外,数组的内容未加密。
    • 在.NET Framework中,内部char数组的内容已加密。由于缺少API或密钥管理问题,.NET并非在所有环境中都支持加密。

建议

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

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

结束编辑:原始摘要如下

很多很好的答案;这是所讨论内容的简要提要。

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

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

当前,SecureString的使用受到限制,但希望将来会得到更好的采用。

根据此信息,SecureString的构造函数不应仅将字符串分割成char数组,因为拼写出字符串会破坏SecureString的目的。

附加信息:

  • 一个岗位由.NET安全作为这里所涉及官方博客谈论大同小异。
  • 另一个 回顾了它,并提到了一种可以转储SecureString内容的工具。

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


19

简短答案

我为什么不能说:

SecureString password = new SecureString("password");

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

长答案

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

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

用零填充内存块。

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

问题是您不能在.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使用Data Protection API将加密的字符串存储在内存中;这样,该字符串将不会存在于swapfiles,崩溃转储中,甚至不会出现在本地变量窗口中,而同事会查看您的应有信息。

如何读取密码?

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

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?那么,您需要做的就是首先停止在String中输入密码。您需要将其包含在其他内容中。甚至一个Char[]数组也会有所帮助。

到那时,您可以附加每个字符在完成后擦除纯文本:

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

你需要存储在您的密码一些记忆,你可以擦拭。从那里将其加载到SecureString中。


tl; dr:SecureString存在以提供等效的ZeroMemory

某些人看不到在锁定设备时从内存清除用户密码,或在他们通过身份验证后从内存中清除键盘击键的意义。这些人不使用SecureString。


14

在极少数情况下,可以在当前版本的Framework中合理使用SecureString。它仅对与非托管API进行交互非常有用-您可以使用Marshal.SecureStringToGlobalAllocUnicode将其编组。

一旦将其转换为System.String或从System.String转换为它,就无法实现其目的。

MSDN 示例从控制台输入一次生成一个SecureString字符,并将安全字符串传递给非托管API。这相当令人费解且不现实。

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

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

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

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

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


12

我相信您必须执行字符附加操作而不是一个平面实例化的原因是因为在后台将“密码”传递给SecureString的构造函数会使“密码”字符串在内存中无法达到安全字符串的目的。

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

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


11

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


5

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


4

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


4

嗯,正如描述所指出的那样,该值是以加密方式存储的,这意味着您的进程的内存转储将不会显示该字符串的值(无需进行相当认真的工作)。

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


2
如果它们限制从常量字符串构造,则foreach行(“ password”中的char c.ToCharArray())会否定它,不是吗?它应该是pass.AppendChar('p'); pass.AppendChar('a');等吗?
理查德·摩根

是的,您可以轻松地抛弃SecureString给您的一点保护。他们正在努力使自己完全无法用脚射击。显然,必须有某种方法可以将值传入和传出SecureString,否则您将无法将其用于任何用途。
Mark Bessey

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.