众所周知,C#的string
相当不安全,它无法被固定在RAM中,GC可移动、复制并在RAM中留下多个跟踪,并且RAM可能被交换并作为文件可供阅读,更不用提其他几个已知事实了。
为了减轻这一问题,Microsoft推出了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...
这种方式会使得密码原始的string
被BCrypt
方法多次复制,甚至在其中更多次。
这让我有一些问题:
1 - BSTR
字符串是否像C
字符串一样具有内存管理的意义?它们是否固定在RAM中,我可以根据自己的需要操纵和删除它们,从而更安全地在C#中使用它们或者将其与C++代码进行交互,以便消除大量的.NET string
操作(如验证密码字符串是否为空或空白,长度是否正确,密码强度是否符合要求)?
2 - 如果问题1的答案是“是”,那么我是否应该花费一些时间将BSTR字符串传递给Interop C++代码来计算其哈希值和验证?
3 - 我的后续代码是否正确,或者我在SecureString
操作方面漏掉了什么?
只是为了明确:我提出这个问题的主要目的是评估是否应该将所有密码处理代码从我的C#代码转移到C++中(可能使用C++/CLI或Interop),以实现比C#提供更安全的代码。 在密码字符串哈希方面,将创建多个字符串并将用户的秘密信息分散在RAM中,这种情况让我非常关注。
例如:我可以编写如下的方法:
public string SecureHash(SecureString password)
这种方法将提取 SecureString
值并将其作为 BSTR
字符串(或其他提取选项,我真的不知道它们之间的优劣)传递给 C++ 哈希方法,以便我们可以控制密码在内存中的所有位置。我这样做是为了避免大量重构我的代码,最后发现我一直错误地使用了
SecureStrings
,而且已经有针对此问题的解决方案了,或者说存在虚假的安全感,有时会发生这种情况,因为我不知道如何在内存中实现 BSTR
字符串或其他 SecureStrings
的提取选项(Unicode、ANSI 等)。
CoTaskMemAnsi
、CoTaskMemUnicode
、GlobalAllocAnsi
和GlobalAllocUnicode
。 - Michel Feinstein