C# Rfc2898DeriveBytes+;PBKDF2+;SecureString是否可以使用安全字符串而不是字符串?
我有一个函数C# Rfc2898DeriveBytes+;PBKDF2+;SecureString是否可以使用安全字符串而不是字符串?,c#,cryptography,securestring,pbkdf2,C#,Cryptography,Securestring,Pbkdf2,我有一个函数GetPassword,它返回一个SecureString类型 当我将此安全字符串传递给Rfc2898DeriveBytes以生成密钥时,Visual Studio显示一个错误。我有限的知识告诉我,这是因为Rfc2898DeriveBytes只接受字符串而不接受安全字符串。有解决办法吗 //read the password from terminal Console.Write("Insert password"); securePwd = myCryptography.GetPa
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);
在做了一些研究并查看了以前关于stackoverflow的答案后,几乎可以肯定的是:“不”。只有API的创建者才能接受
SecureString
,并在内部正确处理它。他们只有在平台的帮助下才能做到这一点
如果您作为一个用户能够检索纯文本
字符串
,那么您首先就否定了使用安全字符串
的大部分优势。这甚至有点危险,因为您将创建外观安全的代码,而实际上根本不安全。显然,您可能会违反SecureString提供的保护,并且
不要从结果中创建字符串
,而是将内容复制到字节[]
以传递到Rfc2898DeriveBytes
。创建字符串
可以防止您破坏密码信息,使其无限期地挂在堆中,或被分页到磁盘,从而增加攻击者找到密码的机会。相反,您应该在使用完密码后立即销毁密码,方法是在数组中填充零。出于同样的原因,在将BSTR
的每个元素复制到字节[]
时,也应该为其分配一个零
应该为每个哈希密码随机选择Salt,而不是一个固定的、可预测的值,否则可能会发生预先计算的字典攻击。为了防止暴力攻击,您应该迭代数万次。我发现有趣的是,该类不支持重载来传递用于派生密钥的密码
WPF允许使用控件将密码作为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);
}
}
private byte[]DeriveKey(SecureString密码、byte[]salt、int迭代次数、int keyByteLength)
{
IntPtr ptr=Marshal.SecureStringToBSTR(密码);
字节[]passwordByteArray=null;
尝试
{
int length=Marshal.ReadInt32(ptr,-4);
passwordByteArray=新字节[长度];
GCHandle=GCHandle.Alloc(passwordByteArray,GCHandleType.pinted);
尝试
{
for(int i=0;i
这种方法利用了以下事实:指针指向数据字符串的第一个字符,前缀为四字节
要点:
- 通过在using语句中包装
,它确保以确定性的方式对其进行处理。这一点很重要,因为它有一个内部Rfc2898DeriveBytes
对象,它是一个HMACSHA1
,需要在调用Dispose时将其拥有的密钥(密码)的副本置零。有关详细信息,请参阅KeyedHashAlgorithm
- 一旦我们完成了
我们就将其归零并通过释放BSTR
- 最后,我们将密码副本归零(清除)
- 更新:添加了
的固定。正如本文注释中所讨论的,如果未固定字节[]
,则垃圾收集器可能会在收集过程中重新定位对象,我们将无法将原始副本归零李>字节[]
SecureString
的收益。尽管如此,如果攻击者能够访问RAM,您可能会遇到更大的问题。另一点是,我们只能管理自己的密码副本,我们正在使用的API很可能会对它们的副本进行错误管理(而不是归零/清除)。据我所知,Rfc2898DeriveBytes
的情况并非如此,尽管它们的byte[]
键(密码)的副本没有固定,因此,如果数组在归零之前在堆中移动,数组的跟踪可能会挂起。这里的信息是,代码看起来是安全的,但问题可能就在下面
如果有人在这个实现中发现任何严重的漏洞,请让我知道 …哦,是的,我不知道为什么salt是这样的:我现在做的是,我使用securePwd。ToString的问题是,它使使用secureString的整个过程变得毫无价值:DIt是,从
//读取密码f开始