Rfc2898DeriveBytes + PBKDF2 + SecureString是否可以使用安全字符串而不是字符串?

时间:2012-03-16 08:31:10

标签: 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);

3 个答案:

答案 0 :(得分:6)

显然,您可以违反SecureStringexpose 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[]密钥(密码)的副本没有固定,因此如果它被移动到数组中,则数组的痕迹可能会挂起。在被清零之前堆。这里的消息是代码看起来很安全,但问题可能在于下面。

如果有人在此实施中发现任何严重漏洞,请告知我们。