如何在C#中正确处理密码

mFe*_*ein 15 c# c++ memory security string

众所周知,C#string非常不安全,它没有固定在RAM中,垃圾收集器可以移动它,复制它,在RAM中留下多个痕迹,RAM可以交换并作为要读取的文件提供,没有提到其他几个已知的事实.

为了缓解这一点,微软想出了这个SecureString.问题是:使用它的正确方法是什么?

我问这个,因为迟早你将不得不提取内部字符串.是的,SecureString确实允许我们char一次写一个并给我们一些其他的小用法,同时隐藏它的秘密,但有时我们需要整个字符串或类似的东西.到目前为止,我看到的所有实现string最终都使用常规,使用SecureString我认为(或希望)的提取和解密内容在某些情况下可以避免,即使它可能涉及一些复杂的代码来绕过.NET string.

在我的例子中,我使用一个名为BCrypt的密码哈希库.它使用常规string来计算哈希值,我认为这远非理想.我没有找到任何可以SecureString直接接受的库,所以我没有太多的选择但是使用它.

目前我运行的代码如下:

SecureString password; // This usually comes from a PasswordBox Property
IntPtr passwordBSTR = Marshal.SecureStringToBSTR(password);

string insecurePassword = Marshal.PtrToStringBSTR(passwordBSTR);
Marshal.ZeroFreeBSTR(passwordBSTR);
passwordBSTR = default(IntPtr);
password.Clear();

// Use the insecurePassword....usually as:
bool result = BCrypt.Net.BCrypt.Verify(insecurePassword, user.Password); // This hashes the provided password and compares it with another BCrypt hash.

insecureString = string.Empty; // hoping for the GC to act now, fingers crossed.

// use result and etc...
Run Code Online (Sandbox Code Playgroud)

这样,密码raw string将被复制多次以用于该BCrypt方法,并且可能在其中多次复制.

这让我想到了一些问题:

1 - 在内存管理方面,字符串是否BSTR像字符串一样C它们是否固定在RAM中我可以操作它们并按照我认为合适的方式擦除它们,因此在C#中使用它们或使用C++代码进行互操作会更安全,因此我可以消除很多我的.NET string操作(如验证是否密码字符串是空格或空格,还是具有正确的长度或具有所需的密码强度)?

2 - 如果问题的#1答案为"是",那么我是否应该投入一些时间将BSTR字符串传递给互操作C++代码来计算它的散列并验证?

3 - 我的后期代码是正确的还是我在SecureString操作时遗漏了什么?

需要明确的是:我对这个问题的主要意图是评估是否应该将我的所有密码处理代码从C#(可能使用C++/CLI或interop)传递给C++,以实现比C#提供的更安全的代码.这个场景真的让我担心密码字符串散列是多个字符串将被创建和复制,用户的秘密信息散布在整个RAM中.

举个例子:我可以制作一个方法:

public string SecureHash(SecureString password)
Run Code Online (Sandbox Code Playgroud)

这个方法将提取SecureString值并将其作为BSTR字符串(或其他提取选项,老实说,我不知道它们之间的好处)传递给C++哈希方法,因此我们可以控制密码在内存中的所有位置.

我要求这没有重大的工作重构我的代码,最后发现它一无所获,因为我一直在SecureStrings错误地使用并且已经有了解决方案,或者有一种虚假的安全感,有时会发生这种情况,因为我不知道BSTR字符串是如何在内存或其他SecureStrings 提取选项(Unicode,ANSI等)中实现的.

joh*_*job 2

  1. Marshal.SecureStringToBSTR() 将在非托管内存中创建一个 BSTR 字符串(即字节数组)并返回指向它的指针;这意味着基础价值已被固定。该值本身是表示字符串的 Unicode 字符序列,以字符串长度为前缀,后跟两个后续空字符 ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069(v= vs.85).aspx )。另一方面,Marshal.PtrToStringBSTR() 将返回一个 .Net System.String 对象,这是不安全的。
  2. 我想说你的选择是:

    • 将 SecureString 对象转换为非托管对象,然后传递给计算哈希值的任何非托管库
    • 在堆栈中的任何地方使用 SecureString,在您的情况下,这意味着重写 BCrypt.Net 以使用 SecureString 对象(该库是开源的并且非常小: https: //bcrypt.codeplex.com/SourceControl/latest#BCrypt.Net/ BCrypt.cs),或者找到替代的托管实现。但由于 SecureString 不提供读取其数据的方法,因此您需要在某个时候将其转换为很可能是非托管字符串(不过您可以使用托管代码中的非托管数据)。
  3. 您缺少对 SecureString 的正确处理(在“使用”或“尝试/最后”中)。