C# SecureString Question
Is there any way to get the value of a SecureString without comprising security? For example, in the code bel开发者_高级运维ow as soon as you do PtrToStringBSTR the string is no longer secure because strings are immutable and garbage collection is non-deterministic for strings.
IntPtr ptr = Marshal.SecureStringToBSTR(SecureString object);
string value = Marshal.PtrToStringBSTR(ptr);
What if there were a way to get a char[] or byte[] of the unmanaged BSTR string? Would that mean garbage collection is more predictable (since you would be using a char[] or byte[] rather than a string? Is this assumption correct, and if so, how would you get back the char[] or byte[]?
Here's a class I've written especially for this purpose. Is it completely, 100% hackproof? No - there's very little you can do to make an application 100% safe, but this class goes about as far as you can to protect yourself if you need to convert a SecureString
into a String
.
Here's how you use the class:
using(SecureStringToStringMarshaler sm = new SecureStringToStringMarshaler(secureString))
{
// Use sm.String here. While in the 'using' block, the string is accessible
// but pinned in memory. When the 'using' block terminates, the string is zeroed
// out for security, and garbage collected as usual.
}
Here's the class
/// Copyright (C) 2010 Douglas Day
/// All rights reserved.
/// MIT-licensed: http://www.opensource.org/licenses/mit-license.php
using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
namespace DDay.Base
{
public class SecureStringToStringMarshaler : IDisposable
{
#region Private Fields
private string _String;
private SecureString _SecureString;
private GCHandle _GCH;
#endregion
#region Public Properties
public SecureString SecureString
{
get { return _SecureString; }
set
{
_SecureString = value;
UpdateStringValue();
}
}
public string String
{
get { return _String; }
protected set { _String = value; }
}
#endregion
#region Constructors
public SecureStringToStringMarshaler()
{
}
public SecureStringToStringMarshaler(SecureString ss)
{
SecureString = ss;
}
#endregion
#region Private Methods
void UpdateStringValue()
{
Deallocate();
unsafe
{
if (SecureString != null)
{
int length = SecureString.Length;
String = new string('\0', length);
_GCH = new GCHandle();
// Create a CER (Contrained Execution Region)
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
// Pin our string, disallowing the garbage collector from
// moving it around.
_GCH = GCHandle.Alloc(String, GCHandleType.Pinned);
}
IntPtr stringPtr = IntPtr.Zero;
RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(
delegate
{
// Create a CER (Contrained Execution Region)
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
stringPtr = Marshal.SecureStringToBSTR(SecureString);
}
// Copy the SecureString content to our pinned string
char* pString = (char*)stringPtr;
char* pInsecureString = (char*)_GCH.AddrOfPinnedObject();
for (int index = 0; index < length; index++)
{
pInsecureString[index] = pString[index];
}
},
delegate
{
if (stringPtr != IntPtr.Zero)
{
// Free the SecureString BSTR that was generated
Marshal.ZeroFreeBSTR(stringPtr);
}
},
null);
}
}
}
void Deallocate()
{
if (_GCH.IsAllocated)
{
unsafe
{
// Determine the length of the string
int length = String.Length;
// Zero each character of the string.
char* pInsecureString = (char*)_GCH.AddrOfPinnedObject();
for (int index = 0; index < length; index++)
{
pInsecureString[index] = '\0';
}
// Free the handle so the garbage collector
// can dispose of it properly.
_GCH.Free();
}
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
Deallocate();
}
#endregion
}
}
This code requires that you can compile unsafe
code, but it works like a charm.
Regards,
-Doug
This should help you: Marshaling SecureString Passwords to String
From the article, the key points are:
- Pin the string in memory.
- Use managed pointers to mutate the System.String.
- Use the strong guarantees of the ExecuteCodeWithGuaranteedCleanup method.
SecureStrings are only secure as long as you don't use them. )-;
The 1 thing you should not do is copy to a string (regardless of the method). The string is immutable and can potentially stay in memory for a long time.
Copying it to a char[] is a little safer as long as you take the precaution of zeroing that array as soon as possible. But the array is present in memory for some time and that is a security risk (breach).
Unfortunately, there is very little support for SecureStrings in the library. The most common way of working with them is one char at a time.
Edit:
the char[]
array should be pinned, and Mark Byers provides a link to an article doing the same thing with a pinned string. It's a matter of choice but the risk of the string is that it is very easy to have it copied (pass it to some method that performs a Trim()
would be enough).
The link Mark provided is about the best you can do, and is the approach my team has taken to address this problem (although we didn't go to the complexity of using CERs). I was a little dubious about using pinning to essentially break C# String immutability, but it does work.
Use Marshal.ZeroFreeBSTR:
EDIT: Yes, creating a new String will create a copy, so you will lose control over cleanup of the contents. You can access the char[] by casting the pointer returned by IntPtr.ToPointer() in an unsafe context:
IntPtr ptr = Marshal.SecureStringToBSTR(str);
unsafe
{
char *cp = (char*)ptr.ToPointer();
//access char[] through cp
}
Marshal.ZeroFreeBSTR(ptr);
Here's a function that frees the native buffer as well, so you don't have the string in memory.
protected static string ConvertToUnsecureString(SecureString securePassword)
{
if (securePassword == null)
throw new ArgumentNullException("securePassword");
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
return Marshal.PtrToStringUni(unmanagedString);
}
finally
{
// Free the native buffer
Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
}
}
Source
精彩评论