views:

547

answers:

5

Is there any way to get the value of a SecureString without comprising security? For example, in the code below 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[]?

A: 

Just a thought: Could not just cast to a CharArray from the string and remove the string from memory.

char[] charray = value.toCharArray();
value = string.Empty();
Tony
I think you are missing the point which is that as soon you create a managed string it's no longer secure.
Taylor Leese
No!! The old instance of value remain in memory. `value=xxx` only overwrites the reference 'value' with another reference.
Henk Holterman
+4  A: 
Mark Byers
Thats a good article, but its absolutely bonkers - not only that but you've still had the string in memory for some time, quick admin access on the machine -> hook in a debugger = thanks for your password. Can anyone really see the point of these?
Kieran Benton
My thoughts are that it would be easier to just use a char[] rather than having to use "pinned" strings. That's what I was looking for with this question.
Taylor Leese
@Kieran - It does reduce the amount of time the string is available and ensures it's in one place. What I was looking to do is something similar with char[] but w/o all the additional complications of having to use a "pinned" string. This way I could just zero out the char[] when I'm done with it. I'm not clear on how to get a char[] from the unmanaged BSTR string though.
Taylor Leese
Taylor L: you could use a char[] instead but you would still have to pin it. If you didn't the GC could relocate it during a garbage collection, and then you'd have no way to zero original copy.
Mark Byers
I see, well that definitely limits their usability.
Taylor Leese
A: 

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);
Lee
That only removes the unmanaged BSTR string. It wouldn't remove "string value". This doesn't make it anymore secure.
Taylor Leese
+1  A: 

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.

donovan
A: 

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

Doug