简短回答
why can't I just say:
SecureString password = new SecureString("password");
现在您的内存中有一个 password
,却没有办法将其清除 - 这正是SecureString存在的原因。
详细回答
SecureString 存在的原因是因为在使用CLR时无法使用 ZeroMemory 来清除敏感数据。它的存在是为了解决由 CLR 引起的问题。
在普通的本机应用程序中,您将调用 SecureZeroMemory
:
用零填充一块内存。
注意:SecureZeroMemory 与ZeroMemory
相同,只是编译器不会将其优化掉。
问题在于你无法在.NET中调用ZeroMemory或SecureZeroMemory。而在.NET中,字符串是不可变的;你甚至不能像在其他语言中那样覆盖字符串的内容。请注意,这段话中有一些HTML标签。
//Wipe out the password
for (int i=0
password[i] = \0
那么你能做什么呢?当我们使用完一个密码或信用卡号时,如何在 .NET 中提供清除内存的能力呢?
唯一的方法是将字符串放在某个本地内存块中,例如:
- BSTR
- HGLOBAL
- CoTaskMem 非托管内存
然后可以调用 ZeroMemory
。.NET 中的字符串无法在使用完毕后被清除:
- 它们是不可变的;您无法覆盖其内容
- 您无法
Dispose
它们
- 它们的清理取决于垃圾回收器
SecureString 存在作为一种安全传递字符串的方式,并且能够在需要时保证其清理。
你问了这个问题:
why can't I just say:
SecureString password = new SecureString("password");
因为现在你在内存中有一个密码;没有办法擦除它。它会一直停留在那里,直到CLR决定重用那个内存。你让我们回到了起点;一个运行中的应用程序有一个无法摆脱的密码,并且可以通过内存转储(或进程监视器)查看密码。
SecureString使用数据保护API将字符串加密存储在内存中;这样,该字符串将不会存在于交换文件、崩溃转储甚至本地变量窗口中,即使同事正在查看您的屏幕。
如何读取密码?
然后问题来了:我该如何与字符串交互?你绝对不想要这样的方法:
String connectionString = secureConnectionString.ToString()
因为现在你又回到了最初的地方 - 一个无法摆脱的密码。你希望强制开发人员正确处理敏感字符串,以便可以从内存中擦除。这就是为什么.NET提供了三个方便的辅助函数来将SecureString编组为非托管内存的原因。
你需要将字符串转换为非托管内存块,处理它,然后再次清除它。
一些API接受安全字符串。例如,在ADO.net 4.5中,SqlConnection.Credential采用了一组SqlCredential:
SqlCredential cred = new SqlCredential(userid, password)
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中拥有密码。你需要将它放到其他地方,即使是Char[]数组也会有所帮助。
这时,当你完成操作后,你可以添加每个字符并清除明文:
for (int i=0; i < PasswordArray.Length; i++)
{
password.AppendChar(PasswordArray[i]);
PasswordArray[i] = (Char)0;
}
你需要将密码存储在可以清除的
某个内存中。然后将其加载到SecureString中。
简单来说: SecureString 旨在提供与 ZeroMemory 相同的功能。
有些人不明白为什么需要在设备锁定后从内存中擦除用户密码或在身份验证后擦除按键记录,这些人不使用 SecureString。
其中
wiping the user's password from memory when a device is locked 意为“在设备锁定后从内存中擦除用户密码”,而
wiping keystrokes from memory after they'authenticated 则是指“在身份验证后擦除按键记录”。
SecureString
进行新开发:https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md - Matt Thomas