Noo*_*Tom 5 c# cryptography securestring pbkdf2
我有一个函数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);
Run Code Online (Sandbox Code Playgroud)
显然,您可以通过该功能违反由其提供的保护SecureString并暴露其内部状态Marshal.SecureStringToBSTR().
而不是创建String结果,将内容复制Byte[]到传递给Rfc2898DeriveBytes.创建一个String会阻止你破坏密码信息,允许它无限期地挂在堆中,或者被分页到磁盘,这反过来又增加了攻击者找到它的机会.相反,您应该在完成使用后立即销毁密码,方法是使用零填充数组.出于同样的原因,您还应该在BSTR将其复制到的每个元素时为其分配零Byte[].
应为每个散列密码随机选择Salt,而不是固定的可预测值,否则可能会预先计算字典攻击.您应该迭代数万次以防止暴力攻击.
在做了一些研究并查看了之前提到的 stackoverflow 上的答案之后SecureString,这个答案几乎肯定是:“不”。只有API的创建者才能SecureString在内部接受并正确处理。他们只能在平台的帮助下做到这一点。
如果您(作为用户)可以检索纯文本,那么您一开始String就否定了使用的大部分优点。SecureString它甚至会有点危险,因为您将创建看起来安全的代码,但实际上根本不安全(编辑:至少在保护内存数据时不安全)。
我发现有趣的是,Rfc2898DeriveBytes该类不支持SecureString传递用于派生密钥的密码的重载。
WPF允许将密码作为控件SecureString对象处理PasswordBox。似乎是一种浪费,由于我们无法将a传递SecureString给构造函数,因此失去了该控件提供的附加安全性。但是,Erickson提出了使用byte[]替代而不是string重载的优点,因为正确地管理byte[]内存中的内容比使用a 相对容易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);
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法利用了一个事实,即BSTR是一个指针,该指针指向带有四个字节长度前缀的数据字符串的第一个字符。
重要事项:
Rfc2898DeriveBytes使用语句,可以确保以确定的方式处理它。这一点很重要,因为它有一个内部HMACSHA1对象,KeyedHashAlgorithm并且需要在调用Dispose时将其拥有的密钥(密码)副本清零。有关完整的详细信息,请参见参考源。BSTR其清零并通过ZeroFreeBSTR释放它。byte[]。如该答案的注释中所述,如果byte[]未固定,则垃圾收集器可以在收集期间重新定位对象,而我们将无法将原始副本归零。这样应该将纯文本密码在内存中保留的时间最短,并且不会削弱使用SecureString过多密码的好处。但是,如果攻击者可以访问RAM,则可能会遇到更大的问题。还有一点是,我们只能管理自己的密码副本,我们正在使用的API很可能会错误地管理(而不是零清除/清除)其副本。据我所知,情况并非如此Rfc2898DeriveBytes,尽管它们的byte[]密钥(密码)副本未固定,因此,如果在归零之前将其移入堆,则阵列的痕迹可能会徘徊。此处的信息是代码看起来很安全,但可能存在问题。
如果有人在此实现中发现任何严重漏洞,请告诉我。