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
    对象,它是一个
    KeyedHashAlgorithm
    ,需要在调用Dispose时将其拥有的密钥(密码)的副本置零。有关详细信息,请参阅
  • 一旦我们完成了
    BSTR
    我们就将其归零并通过释放
  • 最后,我们将密码副本归零(清除)
  • 更新:添加了
    字节[]
    的固定。正如本文注释中所讨论的,如果未固定
    字节[]
    ,则垃圾收集器可能会在收集过程中重新定位对象,我们将无法将原始副本归零
这将使明文密码在内存中保留最短的时间,并且不会削弱过多使用
SecureString
的收益。尽管如此,如果攻击者能够访问RAM,您可能会遇到更大的问题。另一点是,我们只能管理自己的密码副本,我们正在使用的API很可能会对它们的副本进行错误管理(而不是归零/清除)。据我所知,
Rfc2898DeriveBytes
的情况并非如此,尽管它们的
byte[]
键(密码)的副本没有固定,因此,如果数组在归零之前在堆中移动,数组的跟踪可能会挂起。这里的信息是,代码看起来是安全的,但问题可能就在下面


如果有人在这个实现中发现任何严重的漏洞,请让我知道

…哦,是的,我不知道为什么salt是这样的:我现在做的是,我使用securePwd。ToString的问题是,它使使用secureString的整个过程变得毫无价值:DIt是,从
//读取密码f开始