我已经找到了一种我想要的方法。我这里的代码还没有完成(在失败的情况下需要更好的异常处理和内存管理),但这里是:
[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, int length);
public unsafe static byte[] HashNew(this SecureString secureString, HashAlgorithm hashAlgorithm)
{
IntPtr bstr = Marshal.SecureStringToBSTR(secureString);
int maxUtf8BytesCount = Encoding.UTF8.GetMaxByteCount(secureString.Length);
IntPtr utf8Buffer = Marshal.AllocHGlobal(maxUtf8BytesCount);
// Here's the magic:
char* utf16CharsPtr = (char*)bstr.ToPointer();
byte* utf8BytesPtr = (byte*)utf8Buffer.ToPointer();
int utf8BytesCount = Encoding.UTF8.GetBytes(utf16CharsPtr, secureString.Length, utf8BytesPtr, maxUtf8BytesCount);
Marshal.ZeroFreeBSTR(bstr);
var utf8Bytes = new byte[utf8BytesCount];
GCHandle utf8BytesPin = GCHandle.Alloc(utf8Bytes, GCHandleType.Pinned);
Marshal.Copy(utf8Buffer, utf8Bytes, 0, utf8BytesCount);
RtlZeroMemory(utf8Buffer, utf8BytesCount);
Marshal.FreeHGlobal(utf8Buffer);
try
{
return hashAlgorithm.ComputeHash(utf8Bytes);
}
finally
{
for (int i = 0; i < utf8Bytes.Length; i++)
{
utf8Bytes[i] = 0;
}
utf8BytesPin.Free();
}
}
它依赖于获取指向原始utf-16字符串和utf-8缓冲区的指针,然后使用
Encoding.UTF8.GetBytes(Char*, Int32, Byte*, Int32)
将转换保持在非托管内存中。