简答
为什么我不能说:
SecureString password = new SecureString("password");
因为现在你的内存中有password;无法擦除 - 这正是 SecureString 的重点。
长答案
SecureString 存在的原因是您在完成后无法使用 ZeroMemory 擦除敏感数据。它的存在是为了解决因为 CLR 存在的问题。
在常规的 native 应用程序中,您会调用 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。本机内存对象,例如:
- 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当前持有的密码为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。