如何在.NET中散列SecureString?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (21)

在.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;
}

我相信这将正确地对字符串进行散列,并且在函数返回时,将正确地从内存中擦除明文的所有副本,假设提供的散列函数表现良好并且不会生成它没有输入的任何副本磨砂本身。我错过了什么吗?

提问于
用户回答回答于

作为Hans的回答的补充,这里有一个关于如何实现hasher的建议。Hans建议将指向非托管字符串的指针传递给哈希函数,但这意味着客户端代码(=哈希函数)需要处理非托管内存。这并不理想。

另一方面,可以通过以下接口的实例替换回调:

interface Hasher {
    void Reinitialize();
    void AddByte(byte b);
    byte[] Result { get; }
}

这样,哈希(虽然它变得稍微复杂一些)可以完全在受管理的土地上实施,而不会泄露安全信息。你HashSecureString会看起来如下:

private static byte[] HashSecureString(SecureString ss, Hasher hasher) {
    IntPtr bstr = Marshal.SecureStringToBSTR(ss);
    try {
        int length = Marshal.ReadInt32(bstr, -4);

        hasher.Reinitialize();

        for (int i = 0; i < length; i++)
            hasher.AddByte(Marshal.ReadByte(bstr, i));

        return hasher.Result;
    }
    finally {
        Marshal.ZeroFreeBSTR(bstr);
    }
}

请注意该finally块以确保非托管内存归零,而不管hasher实例如何工作。

下面是一个简单的(不是很有用的)Hasher实现来说明接口:

sealed class SingleByteXor : Hasher {
    private readonly byte[] data = new byte[1];

    public void Reinitialize() {
        data[0] = 0;
    }

    public void AddByte(byte b) {
        data[0] ^= b;
    }

    public byte[] Result {
        get { return data; }
    }
}
用户回答回答于

我错过了什么吗?

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

byte[] bytes不过数组包含字符串的副本,在GC堆上分配。可能会使用hashed []数组引发垃圾回收。很容易避免,但是当然你很少控制你的进程中的其他线程分配内存并引发一个集合。或者就此而言,当代码开始运行时,背景GC已经在进行中。

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

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

扫码关注云+社区