在.NET中散列SecureString

Mar*_*ond 24 .net c# securestring password-encryption

在.NET中,我们有SecureString类,在你尝试使用它之前一切都很好,因为(例如)散列字符串,你需要明文.在编写一个将散列SecureString的函数时,我已经开始了,给定一个带有字节数组并输出字节数组的散列函数.

private static byte[] HashSecureString(SecureString ss, Func<byte[], byte[]> hash)
{
    // Convert the SecureString to a BSTR
    IntPtr bstr = Marshal.SecureStringToBSTR(ss);

    // BSTR contains the length of the string in bytes in an
    // Int32 stored in the 4 bytes prior to the BSTR pointer
    int length = Marshal.ReadInt32(bstr, -4);

    // Allocate a byte array to copy the string into
    byte[] bytes = new byte[length];

    // Copy the BSTR to the byte array
    Marshal.Copy(bstr, bytes, 0, length);

    // Immediately destroy the BSTR as we don't need it any more
    Marshal.ZeroFreeBSTR(bstr);

    // Hash the byte array
    byte[] hashed = hash(bytes);

    // Destroy the plaintext copy in the byte array
    for (int i = 0; i < length; i++) { bytes[i] = 0; }

    // Return the hash
    return hashed;
}
Run Code Online (Sandbox Code Playgroud)

我相信这将正确地对字符串进行散列,并且在函数返回时将正确地从内存中擦除明文的任何副本,假设提供的散列函数表现良好并且不进行任何不是的输入副本擦洗自己.我在这里错过了什么吗?

Han*_*ant 14

我在这里错过了什么吗?

是的,你有一个相当基础的.当垃圾收集器压缩堆时,您无法清除留下的数组副本.Marshal.SecureStringToBSTR(ss)没问题,因为BSTR是在非托管内存中分配的,因此会有一个不会改变的可靠指针.换句话说,没有问题擦洗那一个.

byte[] bytes不过数组包含字符串的副本,在GC堆上分配.你可能会使用散列[]数组引发垃圾收集.很容易避免但当然你几乎无法控制进程中的其他线程分配内存和引入集合.或者就此而言,代码开始运行时已经在进行的后台GC.

SecureString的要点是永远不要在垃圾收集的内存中拥有该字符串的明文副本.将其复制到托管数组会违反该保证.如果你想使这段代码安全,那么你将不得不编写一个hash()方法,该方法接受IntPtr并只读取该指针.

请注意,如果您的哈希需要匹配在另一台机器上计算的哈希,那么您不能忽略该机器将用于将字符串转换为字节的编码.


M.S*_*amm 5

总是有可能使用非托管的CryptoApiCNG函数。请记住,该SecureString设计是针对非托管使用者设计的,该使用者要完全控制内存管理。

如果您要坚持使用C#,则应先固定临时阵列以防止GC移动,然后才有机会对其进行清理:

private static byte[] HashSecureString(SecureString input, Func<byte[], byte[]> hash)
{
    var bstr = Marshal.SecureStringToBSTR(input);
    var length = Marshal.ReadInt32(bstr, -4);
    var bytes = new byte[length];

    var bytesPin = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try {
        Marshal.Copy(bstr, bytes, 0, length);
        Marshal.ZeroFreeBSTR(bstr);

        return hash(bytes);
    } finally {
        for (var i = 0; i < bytes.Length; i++) { 
            bytes[i] = 0; 
        }

        bytesPin.Free();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 现在,我了解您的评论,并且据我所知,此解决方案确实应该有效。唯一的警告是,客户端可能很想实现“哈希”函数,该函数将“ byte []”参数重用作返回值(计算就地哈希)。这显然将大大失败。我认为这不是一个非常实际的问题。 (2认同)
  • 一个更真实的问题是哈希函数的实现者不应复制该byte []参数。可悲的是,由于性能原因,Crypto名称空间中的大多数哈希器似乎都这样做。哦,好,另一个问题是另一个问题:) (2认同)