我发现有趣的是Rfc2898DeriveBytes https://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes(v=vs.110).aspx类不支持SecureString https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx用于传递派生密钥时使用的密码的重载。
WPF 允许将密码处理为SecureString
对象与PasswordBox https://msdn.microsoft.com/en-us/library/system.windows.controls.passwordbox(v=vs.110).aspx控制。由于我们无法传入一个值,因此该控件提供的附加安全性丢失了,这似乎是一种浪费。SecureString
到构造函数。然而,erickson提出了使用的优点byte[]
而不是string
过载,因为正确管理 a 的内容相对容易byte[]
记忆中比string
.
Using 埃里克森的作为灵感我想出了以下包装器,它应该允许使用受保护的密码的值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 https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069%28v=vs.85%29.aspx是一个指针,指向具有四字节长度前缀的数据字符串的第一个字符。
要点:
- 通过包裹
Rfc2898DeriveBytes
在 using 语句中,它确保以确定性方式处理它。这很重要,因为它有一个内部HMACSHA1
对象是一个KeyedHashAlgorithm
并且需要在调用中将其拥有的密钥(密码)的副本清零Dispose. See 参考来源 https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/rfc2898derivebytes.cs了解完整详情。
- 一旦我们完成了
BSTR
我们将其归零并通过以下方式释放它零自由BSTR https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.zerofreebstr.aspx.
- 最后,我们将密码副本归零(清除)。
-
Update:添加了固定
byte[]
。正如评论中所讨论的answer https://stackoverflow.com/a/1800733/1608786,如果byte[]
如果没有固定,那么垃圾收集器可以在收集期间重新定位对象,并且我们将无法将原始副本清零。
这应该将明文密码在内存中保留最短的时间,并且不会削弱使用的收益SecureString
太多了。不过,如果攻击者可以访问 RAM,您可能会遇到更大的问题。另一点是,我们只能管理我们自己的密码副本,我们使用的 API 很可能会错误地管理(而不是清零/清除)他们的副本。据我所知,情况并非如此Rfc2898DeriveBytes
,虽然他们的副本byte[]
密钥(密码)未固定,因此如果在清零之前将其移入堆中,则数组的痕迹可能会残留。这里的信息是,代码看起来很安全,但问题可能隐藏在下面。
如果有人发现此实施中有任何严重漏洞,请告诉我。