我正试图了解.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的哪个方面?
答案 0 :(得分:107)
目前使用SecureString
的框架的某些部分:
System.Windows.Controls.PasswordBox
控件在内部将密码保存为SecureString(通过PasswordBox::SecurePassword
公开为副本)System.Diagnostics.ProcessStartInfo::Password
属性为SecureString
X509Certificate2
的构造函数需要SecureString
作为密码主要目的是减少攻击面,而不是消除它。 SecureStrings
被“固定”在RAM中,因此垃圾收集器不会移动它或复制它。它还确保纯文本不会写入Swap文件或核心转储中。加密更像是混淆,并且不会阻止确定的黑客,谁能够找到用于加密和解密的symmetric key。
正如其他人所说,你必须逐个字符创建SecureString
的原因是因为第一个明显的缺点:你可能已经将秘密值作为一个普通字符串了,所以什么是关键点?
SecureString
是解决Chicken-and-Egg问题的第一步,因此即使大多数当前场景需要将它们转换回常规字符串以完全使用它们,它们在框架中的存在现在意味着将来更好地支持他们 - 至少到你的计划不一定是薄弱环节的时候。
答案 1 :(得分:36)
很多很棒的答案;这里是对所讨论内容的简要概述。
Microsoft已实施SecureString类,以便为敏感信息(如信用卡,密码等)提供更好的安全性。它会自动提供:
目前,SecureString的使用受到限制,但预计将来会更好地采用。
基于这些信息,SecureString的构造函数不应该仅仅取一个字符串并将其切片到char数组,因为拼写的字符串会破坏SecureString的目的。
其他信息:
编辑:我发现选择最佳答案很难,因为很多信息都很好;太糟糕了,没有辅助的答案选择。
答案 2 :(得分:18)
为什么我不能说:
SecureString password = new SecureString("password");
因为现在你的内存中有password
;无法擦除它 - 这正是 SecureString 的重点。
存在 SecureString 的原因是因为您不能使用 ZeroMemory 来擦除敏感数据。它的存在是为了解决CLR存在因为的问题。
在常规的原生应用程序中,您可以调用SecureZeroMemory
:
用零填充内存块。
注意:SecureZeroMemory与ZeroMemory
,except the compiler won't optimize it away.
问题是您无法在.NET内部调用ZeroMemory
或SecureZeroMemory
。在.NET中,字符串是不可变的;你甚至可以覆盖字符串的内容,就像你可以用其他语言做的那样:
//Wipe out the password
for (int i=0; i<password.Length; i++)
password[i] = \0;
那你能做什么?我们如何提供.NET在我们完成时从内存中擦除密码或信用卡号码的能力?
唯一可行的方法是将字符串放在某个原生内存块中,可以然后调用ZeroMemory
。本机内存对象,例如:
在.NET中,完成它们后无法删除字符串:
Dispose
他们SecureString作为一种传递字符串安全性的方式存在,并且能够在需要时保证它们的清理。
你问了这个问题:
为什么我不能说:
SecureString password = new SecureString("password");
因为现在你的内存中有password
;无法擦拭它。它一直停留在那里直到CLR决定重新使用那个内存。你把我们放回了我们开始的地方;一个带有密码的正在运行的应用程序我们无法摆脱,内存转储(或进程监视器)可以看到密码。
SecureString使用Data Protection 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的密码?
这是一项挑战,但重点是让您考虑安全性。
有时候已经为您提供了功能。例如,WPF PasswordBox 控件可以直接将输入的密码作为 SecureString 返回:
PasswordBox.SecurePassword Property
获取PasswordBox当前持有的SecureString密码。
这很有用,因为您曾经传递过原始字符串的所有地方,现在您的类型系统抱怨SecureString与String不兼容。在将SecureString转换回常规字符串之前,您希望尽可能长时间地使用。
转换SecureString非常简单:
如:
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 。
有些人不会在wiping the user's password from memory when a device is locked中看到这一点,或者正在擦除wiping keystrokes from memory after they'authenticated。那些人不使用SecureString。
答案 3 :(得分:14)
在极少数情况下,您可以在当前版本的Framework中明智地使用SecureString。它实际上只对与非托管API交互有用 - 您可以使用Marshal.SecureStringToGlobalAllocUnicode封送它。
只要将其转换为System.String,就会失败。
MSDN sample一次从控制台输入生成一个字符SecureString,并将安全字符串传递给非托管API。这是相当复杂和不现实的。
您可能希望.NET的未来版本对SecureString有更多支持,这将使其更有用,例如:
SecureString Console.ReadLineSecure()或类似的东西,将控制台输入读入SecureString,而不需要样本中的所有复杂代码。
WinForms TextBox替换,将TextBox.Text属性存储为安全字符串,以便可以安全地输入密码。
与安全相关的API的扩展,允许将密码作为SecureString传递。
如果没有上述内容,SecureString的价值将会有限。
答案 4 :(得分:12)
我相信你必须做一个字符追加而不是一个平面实例化的原因是因为在后台将“密码”传递给SecureString的构造函数会将“密码”字符串放入内存中,从而破坏了安全字符串的目的。
通过附加你只是将一个角色一次放入记忆中,这种记忆可能不会彼此相邻,从而使重建原始字符串变得更加困难。我可能在这里错了,但这就是我向他解释的原因。
该类的目的是防止通过内存转储或类似工具公开安全数据。
答案 5 :(得分:11)
答案 6 :(得分:5)
SecureString的一大好处是,它可以避免由于页面缓存而将数据存储到磁盘的可能性。如果您在内存中有密码然后加载大型程序或数据集,则当您的程序被分页内存不足时,您的密码可能会被写入交换文件。使用SecureString,至少数据不会无限期地以明文形式存在于您的磁盘上。
答案 7 :(得分:4)
我想这是因为字符串是安全的,即黑客不应该阅读它。如果用字符串初始化它,黑客可以读取原始字符串。
答案 8 :(得分:4)
好吧,正如描述所述,该值是加密存储的,意味着您的进程的内存转储不会显示字符串的值(没有一些相当严肃的工作)。
您不能仅仅从常量字符串构造SecureString的原因是因为那时将在内存中具有未加密的字符串版本。限制您分段创建字符串可以降低将整个字符串同时存储在内存中的风险。
答案 9 :(得分:3)
我会停止使用SecureString。看起来PG家伙正在放弃对它的支持。可能甚至将来拉它 - https://github.com/dotnet/apireviews/tree/master/2015-07-14-securestring。
我们应该在.NET Core中的所有平台上从SecureString中删除加密 - 我们应该废弃SecureString - 我们可能不应该在.NET Core中公开SecureString
答案 10 :(得分:1)
另一个用例是当您使用支付应用程序(POS)时,您只是无法使用不可变数据结构来存储敏感数据,因为您是谨慎的开发人员。例如:如果我将敏感的卡片数据或授权元数据存储到不可变的字符串中,那么这些数据在被丢弃后将在内存中可用大量时间的情况总是如此。我不能简单地覆盖它。这种敏感数据保存在内存中的另一个巨大优势是加密。