简答
为什么我不能说:
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 当前作为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。