11

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

inixsoftware
  • 618
  • 2
  • 9
  • 26

5 Answers5

13

Assuming you want to use the byte array and get rid of it as soon as you're done, you should encapsulate the entire operation so that it cleans up after itself:

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 ***/
    }
}

And here's how a use case looks:

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

No muss, no fuss, no plaintext left floating in memory.

Keep in mind that the byte array passed to func() contains the raw Unicode rendering of the plaintext, which shouldn't be an issue for most cryptographic applications.

Eric Lloyd
  • 509
  • 8
  • 19
  • 3
    Eric - I think you have a slight problem with the workArray. it's nice that you're zeroing it, but what if garbage collector will decide to move it around? then you have your exposed data as "garbage" in memory. you need to pin the byte array to memory before placing sensitive data inside – ArielB May 13 '15 at 11:49
  • 1
    Good catch, @ArielB. I've added a GCHandle.Alloc() and handle.Free() to pin down the workArray until it's cleared. Better late than never. Thanks! – Eric Lloyd Sep 12 '17 at 22:50
  • Per @tobias-bauer, I've updated this version (except for the swallowed exception). Good catch, Tobias! – Eric Lloyd Sep 29 '20 at 04:43
3

I modified from the original answer to handle 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);
}
Jeff Pang
  • 161
  • 1
  • 6
  • 2
    Unlike ANSI C strings, BSTRs can contain null characters, so your scan for nulls is invalid. Just use the Length member of the source SecureString (multiply by 2 to get the byte count). – Eric Lloyd Aug 07 '14 at 18:05
1

As I don't have enough reputation points to comment on Eric's answer, I have to make this post.

In my opinion, there is a problem with Eric's code as GCHandle.Alloc(workArray, ...) is done incorrectly. It should not pin the null value of workArray but the actual array which will be created a couple of lines farther down.

Furthermore handle.Free() can throw an InvalidOperationException and therefore I suggest putting it after Marshal.ZeroFreeBSTR(...) to have at least the binary string bstr is pointing to zeroed out.

The amended code would be this:

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 ***/
    }
}

These modifications ensure that the correct byte Array is pinned in memory (changes no. 1 and 2). Furthermore, they avoid having the unencrypted binary string still loaded in memory in case handle?.Free() throws an exception (change no. 3).

  • Good catch on the `GCHandle.Alloc(...)`. See what happens when you don't actually run a unit test against it? I'll update my answer to match. However, you _never, ever_ want to swallow an exception unless you're actually going to do something about the failure. – Eric Lloyd Sep 29 '20 at 04:33
  • @EricLloyd Thanks for updating your answer! I have one more thing about the exception-swallowing: I absolutely agree with your point. However, I suggest putting `handle?.Free()` *after* `Marshal.ZeroFreeBSTR(...)` as the latter function does not throw an exception on failure and ensures that the `bstr` array got zeroed out. I'll adapt my answer accordingly. – Tobias Bauer Sep 29 '20 at 14:40
0

This 100% managed code seems to work for me:

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);
}
Aaron Jensen
  • 25,861
  • 15
  • 82
  • 91
-2

Per this, http://www.microsoft.com/indonesia/msdn/credmgmt.aspx, you can marshal it into a stock C# string and then convert that into an array of bytes:

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

or per this answer, How to convert SecureString to System.String?, you can use Marshal.ReadByte and Marshal.ReadInt16 on the IntPtr to get what you need.

Community
  • 1
  • 1
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • 2
    Although that does defeat the purpose of the `SecureString`, which is to allow for secure in-memory storage of sensitive string data, such as passwords. – Adrian Aug 23 '13 at 00:33
  • I don't want to use `string`, so is there some other way, that will still keep the passwords safe and accomplish the same thing? – inixsoftware Aug 23 '13 at 01:41