Reputation: 678
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
Upvotes: 12
Views: 11006
Reputation: 26749
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);
}
Upvotes: 0
Reputation: 66
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).
Upvotes: 1
Reputation: 519
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.
Upvotes: 14
Reputation: 161
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);
}
Upvotes: 3
Reputation: 74277
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.
Upvotes: -2