8

我有一个函数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);
4

3 回答 3

8

我发现有趣的是,Rfc2898DeriveBytes该类不支持SecureString传递用于派生密钥的密码的重载。

WPF 允许将密码作为SecureString具有控件的对象来处理PasswordBox。由于我们无法将 a 传递SecureString给构造函数,因此失去了该控件提供的附加安全性,这似乎是一种浪费。但是,erickson提出了使用abyte[]而不是重载的优点,因为在内存string中正确管理 a 的内容比 a 更容易。byte[]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);
    }
}

这种方法利用了BSTR是一个指针,该指针指向具有四字节长度前缀的数据字符串的第一个字符。

要点:

  • 通过包装Rfc2898DeriveBytes在 using 语句中,它确保它以一种确定的方式处理。这很重要,因为它有一个内部HMACSHA1对象,它是 a并且需要在调用DisposeKeyedHashAlgorithm时将其拥有的密钥(密码)的副本清零。有关完整详细信息,请参阅参考源
  • 完成后,BSTR我们将其归零并通过ZeroFreeBSTR释放它。
  • 最后,我们将密码副本清零(清除)。
  • 更新:添加了byte[]. 正如这个答案的评论中所讨论的,如果byte[]没有固定,那么垃圾收集器可以在收集过程中重新定位对象,我们将无法将原始副本归零。

这应该将明文密码保留在内存中的时间最短,并且不会削弱使用SecureString过多的收益。虽然,如果攻击者可以访问 RAM,您可能会遇到更大的问题。另一点是我们只能管理我们自己的密码副本,我们使用的 API 很可能管理不善(不是清零/清除)他们的副本。据我所知,情况并非如此Rfc2898DeriveBytes,尽管他们的密钥(密码)副本byte[]未固定,因此如果在清零之前将其移入堆中,则数组的痕迹可能会残留。这里的信息是代码看起来很安全,但问题可能隐藏在下面。

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

于 2017-05-08T22:09:14.680 回答
6

显然,您可以违反由该函数提供的保护SecureString并通过该函数公开其内部状态。Marshal.SecureStringToBSTR()

与其从结果中创建 a ,不如String将内容复制到 aByte[]以传递给Rfc2898DeriveBytes。创建 aString将阻止您破坏密码信息,使其无限期地挂在堆中,或被分页到磁盘,这反过来又增加了攻击者找到它的机会。相反,您应该在使用完密码后立即销毁密码,方法是用零填充数组。出于同样的原因,您还应该在将 的每个元素BSTR复制到 时为其分配一个零Byte[]

应该为每个散列密码随机选择盐,而不是一个固定的、可预测的值,否则可能会发生预先计算的字典攻击。您应该迭代数万次以防止暴力攻击。

于 2012-03-17T15:47:35.453 回答
5

在进行了一些研究并查看了关于 stackoverflow 提及的先前答案之后SecureString,该答案几乎可以肯定是:“否”。只有 API 的创建者可以在SecureString内部正确地接受和处理它。他们只能在平台的帮助下做到这一点。

如果您 - 作为用户 - 可以检索纯文本String,那么您首先会否定使用的大部分优势SecureString。它甚至会有点危险,因为您将创建看起来安全的代码,实际上根本不安全(编辑:至少在保护内存数据方面不是)。

于 2012-03-17T15:25:24.360 回答