27

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[]?

Taylor Leese
  • 51,004
  • 28
  • 112
  • 141
  • 4
    I know what you mean - I've never understood the point of them either, they've always got to get casted to an array at some point! – Kieran Benton Nov 25 '09 at 23:40

6 Answers6

35

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
  • 5,208
  • 3
  • 29
  • 33
  • Thank you for your help. I would like to know, though; why do you use CERs? What would be the disadvantage of not using them? Why not use a standard `try`/`catch` instead of `ExecuteCodeWithGuaranteedCleanup`? – Jean Hominal May 03 '11 at 14:40
  • 4
    The CER is used to guarantee that the allocated handle is not accidentally leaked. In essence, it prevents asynchronous exceptions from firing which would result in a memory leak. ExecuteCodeWithGuaranteedCleanup guarantees the string will be zeroed out even in extraneous circumstances, like a StackOverFlowException (which would leak the secure string contents in a StackOverflow if ExecuteCodeWithGuaranteedCleanup weren't used). See the following article for a great explanation of the reliability features of .NET: http://msdn.microsoft.com/en-us/magazine/cc163716.aspx – Doug May 23 '11 at 16:02
  • VB.net doesn't seem to support unsafe, so translating this code implementation is restricted to .net languages that support running unsafe code. I'm struggling to understand why dot net hasn't got better API's around this to make a developers life easier. – Jamie Clayton Aug 15 '12 at 01:31
  • Thanks, this was a very thoughtful solution. – code4life Nov 01 '14 at 02:55
  • 1
    I can't check now, and even if I'm right it's not the worst thing in the world, but just something to be aware of: I think this will leak the length of the sensitive data. IIRC the mem allocated for a BSTR is at least 5 bytes longer than the char buffer it points to: four bytes at buf[-3]...buf[-1] to store the length, and a byte at buf[len] for a null. So if you define a string outside the "using" block, and while in the block you set it equal to the sensitive string, then even after exiting the block, it won't be string.Empty, but a zero-filled string with the length of the sensitive data. – Bob Vesterman Nov 08 '21 at 14:05
  • @BobVesterman that sounds correct to me, but it's a slim period of time. Once the string is deallocated, not even the length will be leaked. Just the nature of the beast. – Doug Nov 11 '21 at 02:18
  • @Doug sure, like I said, it's not the worst thing in the world, but it is something that people who use it should be aware of. Also, I just want to take this opportunity to note that my phrasing in my first comment might have slightly underplayed the (still-slight) severity: I meant the "if you define a string outside the using block" thing only as an example. I believe it will remain present in memory regardless (until actual deallocation of the underlying BSTR). – Bob Vesterman Nov 11 '21 at 02:40
  • Oh, and I should mention: I thought about maybe trying to zero out the length too (during the disposal), but then I realized that even if it would work when I tried it, I don't know enough about stuff at that low a level to say for sure that the buffer that underlies the BSTR that underlies the System:string really would be deallocated. And even if I could see that happening, I don't know enough to say that it would always happen in all situations. So, I just gave up on it. Just throwing the idea out there now in case someone more knowledgeable than me might be interested in trying. – Bob Vesterman Nov 11 '21 at 02:47
  • To be honest, I wrote this post in 2010. If you're using SecureString these days it's almost always because you're using legacy systems, as it's very unusual to see it in active use today (in my experience). – Doug Nov 11 '21 at 17:56
13

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.
Barn Monkey
  • 245
  • 3
  • 11
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 4
    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 Nov 25 '09 at 23:56
  • 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 Nov 26 '09 at 00:00
  • @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 Nov 26 '09 at 00:02
  • 4
    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 Nov 26 '09 at 00:06
  • I see, well that definitely limits their usability. – Taylor Leese Nov 26 '09 at 00:25
  • I wonder why SecureString doesn't provide an indexed getter? That would allow code to make use of its contents for a variety of purposes without ever having to store them anywhere in a usable decrypted form. – supercat Aug 27 '15 at 17:52
9

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).

H H
  • 263,252
  • 30
  • 330
  • 514
  • Any ideas on how to get the char[] or byte[]? Is garbage collection for primitive arrays more deterministic than strings? – Taylor Leese Nov 25 '09 at 23:48
  • 1
    1) through BSTR. 2) No, the only advantage of arrays is that you _can_ zero them. But you should make sure that that gets done ASAP. – H H Nov 25 '09 at 23:54
  • Can you expand on #1 (through BSTR)? – Taylor Leese Nov 26 '09 at 00:03
  • 2
    You can adopt step 4 from the link in Mark Byers' answer. But it should be a pinned `char[]`, because otherwise the GC could move it (and leave the old copy out of your control). – H H Nov 26 '09 at 00:24
  • 2
    Sounds like the easiest answer is just not to use them. :-) – Taylor Leese Nov 26 '09 at 00:31
1

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
  • 1,442
  • 9
  • 18
0

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
  • 142,018
  • 20
  • 234
  • 287
  • That only removes the unmanaged BSTR string. It wouldn't remove "string value". This doesn't make it anymore secure. – Taylor Leese Nov 25 '09 at 23:47
  • @TaylorLeese What "string value"? This code never brings the string into managed world. I've never used unsafe code myself, but it sure seems that cp gives you access to the char[], as long as you are in the unsafe block. – Sean Hall Oct 01 '12 at 15:48
0

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

SaiyanGirl
  • 16,376
  • 11
  • 41
  • 57