我有一个函数GetPassword
,它返回SecureString
类型。
当我将此安全字符串传递给Rfc2898DeriveBytes
以生成密钥时,Visual Studio会显示错误。我的有限知识告诉我,这是因为Rfc2898DeriveBytes
只接受字符串而不是安全字符串。有解决方法吗?
//read the password from terminal
Console.Write("Insert password");
securePwd = myCryptography.GetPassword();
//dont know why the salt is initialized like this
byte[] salt = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
try
{ //PBKDF2 standard
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(securePwd, salt, iterationsPwd);
答案 0 :(得分:6)
显然,您可以违反SecureString
和expose its internal state via the Marshal.SecureStringToBSTR()
function.提供的保护
不是在结果中创建String
,而是将内容复制到Byte[]
以传递给Rfc2898DeriveBytes
。创建String
会阻止您破坏密码信息,允许它无限期地挂在堆中,或者被分页到磁盘,从而增加了攻击者找到它的机会。相反,您应该在完成使用后立即销毁密码,方法是使用零填充数组。出于同样的原因,在将BSTR
复制到Byte[]
时,还应为{{1}}的每个元素指定零。
应为每个散列密码随机选择Salt,而不是固定的可预测值,否则可能会预先计算字典攻击。你应该迭代数万次,以防止暴力攻击。
答案 1 :(得分:4)
在做了一些研究并查看有关提及SecureString
的stackoverflow的先前答案后,答案几乎肯定是:“否”。只有API的创建者才能接受SecureString
并在内部正确处理它。他们只能在平台的帮助下做到这一点。
如果您 - 作为用户 - 可以检索纯文本String
,那么您首先会否定使用SecureString
的大部分优势。它甚至会有点危险,因为你会创建安全的代码,实际上根本不安全。
答案 2 :(得分:4)
我发现有趣的是Rfc2898DeriveBytes
类不支持SecureString
重载来传递用于导出密钥的密码。
WPF允许使用PasswordBox
控件将密码作为SecureString
个对象进行处理。由于我们无法将SecureString
传递给构造函数,因此这种控件提供的额外安全性似乎已经丢失,这似乎是一种浪费。但是, erickson 提出了使用byte[]
代替string
重载的优点,因为相对容易正确管理byte[]
的内容记忆而不是string
。
使用 erickson的建议作为灵感,我想出了以下包装器,它应该允许使用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
内部KeyedHashAlgorithm
对象,并且需要在 Dispose 的调用中将其拥有的密钥(密码)副本清零。 EM>。有关详细信息,请参阅Reference Source。BSTR
后,我们将其归零并通过ZeroFreeBSTR释放。byte[]
的固定。正如此answer的注释中所讨论的,如果byte[]
没有固定,那么垃圾收集器可以在收集期间重新定位对象,我们将无法将原始副本清零。 这应该将明文密码保留在内存中最短的时间,而不是削弱使用SecureString
太多的收益。虽然,如果攻击者可以访问RAM,你可能会遇到更大的问题。另一点是我们只能管理我们自己的密码副本,我们使用的API很可能会错误管理(不清零/清除)他们的副本。据我所知,这不是Rfc2898DeriveBytes
的情况,尽管它们的byte[]
密钥(密码)的副本没有固定,因此如果它被移动到数组中,则数组的痕迹可能会挂起。在被清零之前堆。这里的消息是代码看起来很安全,但问题可能在于下面。
如果有人在此实施中发现任何严重漏洞,请告知我们。