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

时间:2016-09-22 20:00:43

标签: c# c++ string security memory

众所周知,C#string非常不安全,它没有固定在RAM中,垃圾收集器可以移动它,复制它,在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字符串传递给互操作C ++代码来计算它的散列并验证吗?

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

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

作为一个例子:我可以制作一个类似的方法:

public string SecureHash(SecureString password)

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

我要求我没有重大的工作重构我的代码,最后发现它一无所获,因为我一直在错误地使用SecureStrings并且已经有了解决方案,或者有时会发生虚假的安全感,因为我不知道BSTR字符串是如何在内存中实现的,或者是SecureStrings extraction options(Unicode,ANSI等)。< /强>

3 个答案:

答案 0 :(得分: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的正确处理(在“using”或try / finally中)。

答案 1 :(得分:-1)

为什么不使用单向加密(HASH)?使用加密密码保存随机盐并推送这两个值。这样,您可以使用相同的salt来加密要检查的密码,并比较加密值。我会推荐SHA-256或更强。

至于安全性,这几乎与Microsoft在C#AspNet Membership中使用的方法相同。由于这是Dot Net的行业标准,因此这种安全级别也适用于您。

这是一个很好的来源,看看如何做到这一点: http://www.obviex.com/samples/hash.aspx

答案 2 :(得分:-1)

SecureString的扩展方法怎么样:

using System;
using System.Runtime.InteropServices;

public static class SecureStringExtensions 
{
    public static T Decrypt<T>(this SecureString ss, Func<string, T> handler) 
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        var ptr = Marshal.SecureStringToBSTR(ss);
        try {
            return handler(Marshal.PtrToStringBSTR(ptr));
        } finally {
            Marshal.ZeroFreeBSTR(ptr);
        }
    }
}

这样,解密后的字符串就会停留在堆栈中,并会被未来的堆栈变量快速覆盖。