10

How would I get a byte[] equivalent of a SecureString (which I get from a PasswordBox)?

My objective is to write these bytes using a CryptoStream to a file, and the Write method of that class takes a byte[] input, so I want to convert the SecureString to the byte[] so I can use in with a CryptoStream.

EDIT: I don't want to use string as it defeats the point of having a SecureString

4

5 回答 5

12

假设您想使用字节数组并在完成后立即摆脱它,您应该封装整个操作,以便它自行清理:

public static T Process<T>(this SecureString src, Func<byte[], T> func)
{
    IntPtr bstr = IntPtr.Zero;
    byte[] workArray = null;
    GCHandle? handle = null; // Hats off to Tobias Bauer
    try
    {
        /*** PLAINTEXT EXPOSURE BEGINS HERE ***/
        bstr = Marshal.SecureStringToBSTR(src);
        unsafe
        {
            byte* bstrBytes = (byte*)bstr;
            workArray = new byte[src.Length * 2];
            handle = GCHandle.Alloc(workArray, GCHandleType.Pinned); // Hats off to Tobias Bauer
            for (int i = 0; i < workArray.Length; i++)
                workArray[i] = *bstrBytes++;
        }

        return func(workArray);
    }
    finally
    {
        if (workArray != null)
            for (int i = 0; i < workArray.Length; i++)
                workArray[i] = 0;
        handle.Free();
        if (bstr != IntPtr.Zero)
            Marshal.ZeroFreeBSTR(bstr);
        /*** PLAINTEXT EXPOSURE ENDS HERE ***/
    }
}

以下是用例的外观:

private byte[] GetHash(SecureString password)
{
    using (var h = new SHA256Cng()) // or your hash of choice
    {
        return password.Process(h.ComputeHash);
    }
}

没有混乱,没有大惊小怪,没有明文留在记忆中。

请记住,传递给的字节数组func()包含明文的原始 Unicode 渲染,这对于大多数加密应用程序来说应该不是问题。

于 2014-08-07T19:27:56.777 回答
3

我从原始答案修改为处理 unicode

IntPtr unmanagedBytes = Marshal.SecureStringToGlobalAllocUnicode(password);
byte[] bValue = null;
try
{
    byte* byteArray = (byte*)unmanagedBytes.GetPointer();

    // Find the end of the string
    byte* pEnd = byteArray;
    char c='\0';
    do
    {
        byte b1=*pEnd++;
        byte b2=*pEnd++;
        c = '\0';
        c= (char)(b1 << 8);                 
        c += (char)b2;
    }while (c != '\0');

    // Length is effectively the difference here (note we're 2 past end) 
    int length = (int)((pEnd - byteArray) - 2);
    bValue = new byte[length];
    for (int i=0;i<length;++i)
    {
        // Work with data in byte array as necessary, via pointers, here
        bValue[i] = *(byteArray + i);
    }
}
finally
{
    // This will completely remove the data from memory
    Marshal.ZeroFreeGlobalAllocUnicode(unmanagedBytes);
}
于 2014-01-07T05:24:53.337 回答
0

这个 100% 托管的代码似乎对我有用:

var pUnicodeBytes = Marshal.SecureStringToGlobalAllocUnicode(secureString);
try
{
    byte[] unicodeBytes = new byte[secureString.Length * 2];

    for( var idx = 0; idx < unicodeBytes.Length; ++idx )
    {
        bytes[idx] = Marshal.ReadByte(pUnicodeBytes, idx);
    }

    return bytes;
}
finally
{
    Marshal.ZeroFreeGlobalAllocUnicode(pUnicodeBytes);
}
于 2021-01-29T23:38:22.760 回答
0

由于我没有足够的声望点来评论 Eric 的回答,所以我必须发表这篇文章。

在我看来,Eric 的代码存在问题,因为GCHandle.Alloc(workArray, ...)操作不正确。它不应该固定的null值,workArray而是实际的数组,它将在更远的几行中创建。

此外handle.Free()可以抛出一个InvalidOperationException,因此我建议把它Marshal.ZeroFreeBSTR(...)放在至少二进制字符串bstr指向零之后。

修改后的代码是这样的:

public static T Process<T>(this SecureString src, Func<byte[], T> func)
{
    IntPtr bstr = IntPtr.Zero;
    byte[] workArray = null;
    GCHandle? handle = null; // Change no. 1
    try
    {
        /*** PLAINTEXT EXPOSURE BEGINS HERE ***/
        bstr = Marshal.SecureStringToBSTR(src);
        unsafe
        {
            byte* bstrBytes = (byte*)bstr;
            workArray = new byte[src.Length * 2];
            handle = GCHandle.Alloc(workArray, GCHandleType.Pinned); // Change no. 2

            for (int i = 0; i < workArray.Length; i++)
                workArray[i] = *bstrBytes++;
        }

        return func(workArray);
    }
    finally
    {
        if (workArray != null)
            for (int i = 0; i < workArray.Length; i++)
                workArray[i] = 0;
        
        if (bstr != IntPtr.Zero)
            Marshal.ZeroFreeBSTR(bstr);

        handle?.Free(); // Change no. 3 (Edit: no try-catch but after Marshal.ZeroFreeBSTR)

        /*** PLAINTEXT EXPOSURE ENDS HERE ***/
    }
}

这些修改确保正确的byteArray 固定在内存中(更改 1 和 2)。此外,他们避免将未加密的二进制字符串仍然加载到内存中,以防handle?.Free()引发异常(更改号 3)。

于 2020-09-28T14:40:49.700 回答
-2

根据http://www.microsoft.com/indonesia/msdn/credmgmt.aspx,您可以将其编组为股票 C# 字符串,然后将其转换为字节数组:

static string SecureStringToString( SecureString value )
{
  string s ;
  IntPtr p = Marshal.SecureStringToBSTR( value );
  try
  {
    s = Marshal.PtrToStringBSTR( p ) ;
  }
  finally
  {
    Marshal.FreeBSTR( p ) ;
  }
  return s ;
}

或根据这个答案,如何将 SecureString 转换为 System.String?, 你可以使用Marshal.ReadByteMarshal.ReadInt16IntPtr获得你需要的东西。

于 2013-08-23T00:19:25.827 回答