我发现有趣的是,Rfc2898DeriveBytes
该类不支持SecureString
传递用于派生密钥的密码的重载。
WPF 允许将密码作为SecureString
具有控件的对象来处理PasswordBox
。由于我们无法将 a 传递SecureString
给构造函数,因此失去了该控件提供的附加安全性,这似乎是一种浪费。但是,erickson提出了使用abyte[]
而不是重载的优点,因为在内存string
中正确管理 a 的内容比 a 更容易。byte[]
string
以埃里克森的建议为灵感,我想出了以下包装器,它应该允许使用受保护的密码SecureString
值,而内存中明文值的暴露最少。
private byte[] DeriveKey(SecureString password, byte[] salt, int iterations, int keyByteLength)
{
IntPtr ptr = Marshal.SecureStringToBSTR(password);
byte[] passwordByteArray = null;
try
{
int length = Marshal.ReadInt32(ptr, -4);
passwordByteArray = new byte[length];
GCHandle handle = GCHandle.Alloc(passwordByteArray, GCHandleType.Pinned);
try
{
for (int i = 0; i < length; i++)
{
passwordByteArray[i] = Marshal.ReadByte(ptr, i);
}
using (var rfc2898 = new Rfc2898DeriveBytes(passwordByteArray, salt, iterations))
{
return rfc2898.GetBytes(keyByteLength);
}
}
finally
{
Array.Clear(passwordByteArray, 0, passwordByteArray.Length);
handle.Free();
}
}
finally
{
Marshal.ZeroFreeBSTR(ptr);
}
}
这种方法利用了BSTR是一个指针,该指针指向具有四字节长度前缀的数据字符串的第一个字符。
要点:
- 通过包装
Rfc2898DeriveBytes
在 using 语句中,它确保它以一种确定的方式处理。这很重要,因为它有一个内部HMACSHA1
对象,它是 a并且需要在调用DisposeKeyedHashAlgorithm
时将其拥有的密钥(密码)的副本清零。有关完整详细信息,请参阅参考源。
- 完成后,
BSTR
我们将其归零并通过ZeroFreeBSTR释放它。
- 最后,我们将密码副本清零(清除)。
- 更新:添加了
byte[]
. 正如这个答案的评论中所讨论的,如果byte[]
没有固定,那么垃圾收集器可以在收集过程中重新定位对象,我们将无法将原始副本归零。
这应该将明文密码保留在内存中的时间最短,并且不会削弱使用SecureString
过多的收益。虽然,如果攻击者可以访问 RAM,您可能会遇到更大的问题。另一点是我们只能管理我们自己的密码副本,我们使用的 API 很可能管理不善(不是清零/清除)他们的副本。据我所知,情况并非如此Rfc2898DeriveBytes
,尽管他们的密钥(密码)副本byte[]
未固定,因此如果在清零之前将其移入堆中,则数组的痕迹可能会残留。这里的信息是代码看起来很安全,但问题可能隐藏在下面。
如果有人在此实现中发现任何严重漏洞,请告诉我。