How to convert SecureString to System.String?
Asked Answered
S

13

193

All reservations about unsecuring your SecureString by creating a System.String out of it aside, how can it be done?

How can I convert an ordinary System.Security.SecureString to System.String?

I'm sure many of you who are familiar with SecureString are going to respond that one should never transform a SecureString to an ordinary .NET string because it removes all security protections. I know. But right now my program does everything with ordinary strings anyway, and I'm trying to enhance its security and although I'm going to be using an API that returns a SecureString to me I am not trying to use that to increase my security.

I'm aware of Marshal.SecureStringToBSTR, but I don't know how to take that BSTR and make a System.String out of it.

For those who may demand to know why I would ever want to do this, well, I'm taking a password from a user and submitting it as an html form POST to log the user into a web site. So... this really has to be done with managed, unencrypted buffers. If I could even get access to the unmanaged, unencrypted buffer I imagine I could do byte-by-byte stream writing on the network stream and hope that that keeps the password secure the whole way. I'm hoping for an answer to at least one of these scenarios.

Sundries answered 4/5, 2009 at 3:36 Comment(0)
S
222

Use the System.Runtime.InteropServices.Marshal class:

String SecureStringToString(SecureString value) {
  IntPtr valuePtr = IntPtr.Zero;
  try {
    valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
    return Marshal.PtrToStringUni(valuePtr);
  } finally {
    Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
  }
}

If you want to avoid creating a managed string object, you can access the raw data using Marshal.ReadInt16(IntPtr, Int32):

void HandleSecureString(SecureString value) {
  IntPtr valuePtr = IntPtr.Zero;
  try {
    valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
    for (int i=0; i < value.Length; i++) {
      short unicodeChar = Marshal.ReadInt16(valuePtr, i*2);
      // handle unicodeChar
    }
  } finally {
    Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
  }
}
Severini answered 4/5, 2009 at 11:21 Comment(7)
Got my up-vote too even years later, thanks for the help! Just a quick note: this also works as a static, in its own memory.Thereof
I used StopWatch and SecureStringToString took 4.6sec to run. It's to slow for me. Does anyone get the same time or something faster?Vacation
@Vacation In a quick and dirty test setup, I can call it 1000 times in 76ms. The first invocation takes 0.3 ms and subsequent invocations ~0.07ms. How large is your secure string and which version of the framework are you using?Severini
Length om my secureString is 168. I am using .NET Framework 3.5 if that answered your question? I have tryed 5-10 times is always around 4.5-4.65 sec~ I would love to get your timeVacation
@RasmusFaber My bad, I had added a Database.GetConnectionString() into your code, to get my secureString, which was the evil part that took almost 5sec(and yes I should look into that! :) Your code took .00 mili seconds in my stopwatch so it's all good. Thanks for pointing me in the right direction.Vacation
I used the exact code in this answer. Every time I pass in a SecureString value, Marshal.PtrToStringUni returns an Empty String. Any idea why or how to find out what is going wrong?Sparrow
The code should use SecureStringToBSTR because SecureString can contain \0 as non-terminating characters, but SecureStringToGlobalAllocUnicode treat it as a null-terminated string.Parisi
A
152

Obviously you know how this defeats the whole purpose of a SecureString, but I'll restate it anyway.

If you want a one-liner, try this: (.NET 4 and above only)

string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;

Where securePassword is a SecureString.

Acrocarpous answered 9/9, 2014 at 18:53 Comment(8)
Although it does defeat the purpose in production, your solution is perfect for unit tests. Thanks.Scarcity
This helped me to figure out that a SecureString (System.Security.SecureString) was not being passed to my ApiController (webapi). ThxStruble
Note in PowerShell this is [System.Net.NetworkCredential]::new('', $securePassword).PasswordCocklebur
@Cocklebur Just being pedantic, but more accurately: [System.Net.Credential]::new([string]::Empty, $securePassword).Password. Even so, that requires version 5 to work: (New-Object Net.Credential [string]::Empty, $securePassword).PasswordSuperpatriot
@TheIncorrigible1 can you elaborate? E.g. when is '' not the same type as [String]::Empty? Also New-Object Net.Credential doesn't work for me: Cannot find type [Net.Credential]: verify that the assembly containing this type is loadedCocklebur
why does this defeat the purpose? I don't understand.?Motherwell
It defeats the purpose of a SecureString because it makes a non-encrypted copy of your SecureString content in to a normal string. Every time you do that, you are adding at least one (and with Garbage Collection possibly multiple) copies of your unencrypted string to memory. This is considered a risk for some security sensitive applications and SecureString was implemented specifically to reduce the risk.Acrocarpous
@stijn, generally speaking, "" is the same as String.Empty, but the convention is to prefer String.Empty to "" for clarity of intent. Strictly speaking, "" is almost certainly not the same reference as String.Empty in most places in the code. Also, some jackwagon could theoretically change the value of String.Empty via reflection.Wallaby
S
54

Dang. right after posting this I found the answer deep in this article. But if anyone knows how to access the IntPtr unmanaged, unencrypted buffer that this method exposes, one byte at a time so that I don't have to create a managed string object out of it to keep my security high, please add an answer. :)

static String SecureStringToString(SecureString value)
{
    IntPtr bstr = Marshal.SecureStringToBSTR(value);

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
Sundries answered 4/5, 2009 at 3:38 Comment(6)
You can certainly use the unsafe keyword and a char*, just call bstr.ToPointer() and cast.Cleopatracleopatre
@BenVoigt BSTR has a null terminator after the string data for safety, but also allows null characters embedded in the string. So it's a bit more complicated than that, you also need to retrieve the length prefix that sits before that pointer. learn.microsoft.com/en-us/previous-versions/windows/desktop/…Needleful
@WimCoenen: True but unimportant. The length stored in the BSTR will be a copy of the length already available from SecureString.Length.Cleopatracleopatre
@BenVoigt ah, my bad. I thought SecureString didn't expose any information about the string.Needleful
@WimCoenen: SecureString is not trying to hide the value, it's trying to prevent copies of the value from being made into regions which cannot be reliably overwritten, such as garbage collected memory, pagefile, etc. The intention is that when the SecureString lifetime ends, absolutely no copy of the secret remains in memory. It doesn't prevent you from making and leaking a copy, but it never does.Cleopatracleopatre
It's somewhat of a moot point, given that you're creating a managed copy, but it's generally better to call Marshal.ZeroFreeBSTR(), not Marshal.FreeBSTR().Impel
H
21

In my opinion, extension methods are the most comfortable way to solve this.

I took Steve in CO's excellent answer and put it into an extension class as follows, together with a second method I added to support the other direction (string -> secure string) as well, so you can create a secure string and convert it into a normal string afterwards:

public static class Extensions
{
    // convert a secure string into a normal plain text string
    public static String ToPlainString(this System.Security.SecureString secureStr)
    {
        String plainStr = new System.Net.NetworkCredential(string.Empty, 
                          secureStr).Password;
        return plainStr;
    }

    // convert a plain text string into a secure string
    public static System.Security.SecureString ToSecureString(this String plainStr)
    {
        var secStr = new System.Security.SecureString(); secStr.Clear();
        foreach (char c in plainStr.ToCharArray())
        {
            secStr.AppendChar(c);
        }
        return secStr;
    }
}

With this, you can now simply convert your strings back and forth like so:

// create a secure string
System.Security.SecureString securePassword = "MyCleverPwd123".ToSecureString(); 

// convert it back to plain text (normal string)
String plainPassword = securePassword.ToPlainString();  

But keep in mind the decoding method should only be used for testing.


If you're interested in the details: NetworkCredential uses internally

using System.Runtime.InteropServices;
using System.Security;

private string MarshalToString(SecureString sstr)
{
    if (sstr == null || sstr.Length == 0)
    {
        return string.Empty;
    }
    IntPtr intPtr = IntPtr.Zero;
    string empty = string.Empty;
    try
    {
        intPtr = Marshal.SecureStringToGlobalAllocUnicode(sstr);
        return Marshal.PtrToStringUni(intPtr);
    }
    finally
    {
        if (intPtr != IntPtr.Zero)
        {
            Marshal.ZeroFreeGlobalAllocUnicode(intPtr);
        }
    }
}

private unsafe SecureString MarshalToSecureString(string str)
{
    if (string.IsNullOrEmpty(str))
    {
        return new SecureString();
    }
    fixed (char* ptr = str)
    {
        char* value = ptr;
        return new SecureString(value, str.Length);
    }
}

to convert a SecureString into a string and vice versa.

And SecureString internally uses

using System.Runtime.InteropServices;

[DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CryptProtectMemory(SafeBuffer pData, uint cbData, uint dwFlags);

[DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CryptUnprotectMemory(SafeBuffer pData, uint cbData, uint dwFlags);

to encrypt / decrypt the string data (pData buffer) in situ (i.e. in place, without copying it). This means, you don't know how a SecureString is being internally encrypted. But since you can decrypt it with zero effort, it isn't really secure - it just prevents someone looking into a memory dump from finding plain text strings too easily. In other words, if you don't find a string in a memory dump easily, you obviously can't decrypt it (security through obscurity).

Humfrey answered 24/6, 2016 at 14:51 Comment(2)
I believe ToSecureString should only be used for testing as well, because the point of SecureString is to keep the string out of memory.Ptolemaic
@Ptolemaic - Yes. And additionally this isn't as good as true encryption. There is no key used so everyone can show the plaintext. So it shouldn't be named "secure". Another example of "security through obscurity".Humfrey
I
18

I think it would be best for SecureString dependent functions to encapsulate their dependent logic in an anonymous function for better control over the decrypted string in memory (once pinned).

The implementation for decrypting SecureStrings in this snippet will:

  1. Pin the string in memory (which is what you want to do but appears to be missing from most answers here).
  2. Pass its reference to the Func/Action delegate.
  3. Scrub it from memory and release the GC in the finally block.

This obviously makes it a lot easier to "standardize" and maintain callers vs. relying on less desirable alternatives:

  • Returning the decrypted string from a string DecryptSecureString(...) helper function.
  • Duplicating this code wherever it is needed.

Notice here, you have two options:

  1. static T DecryptSecureString<T> which allows you to access the result of the Func delegate from the caller (as shown in the DecryptSecureStringWithFunc test method).
  2. static void DecryptSecureString is simply a "void" version which employ an Action delegate in cases where you actually don't want/need to return anything (as demonstrated in the DecryptSecureStringWithAction test method).

Example usage for both can be found in the StringsTest class included.

Strings.cs

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace SecurityUtils
{
    public partial class Strings
    {
        /// <summary>
        /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
        /// </summary>
        /// <typeparam name="T">Generic type returned by Func delegate</typeparam>
        /// <param name="action">Func delegate which will receive the decrypted password pinned in memory as a String object</param>
        /// <returns>Result of Func delegate</returns>
        public static T DecryptSecureString<T>(SecureString secureString, Func<string, T> action)
        {
            var insecureStringPointer = IntPtr.Zero;
            var insecureString = String.Empty;
            var gcHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

            try
            {
                insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(secureString);
                insecureString = Marshal.PtrToStringUni(insecureStringPointer);

                return action(insecureString);
            }
            finally
            {
                //clear memory immediately - don't wait for garbage collector
                fixed(char* ptr = insecureString )
                {
                    for(int i = 0; i < insecureString.Length; i++)
                    {
                        ptr[i] = '\0';
                    }
                }

                insecureString = null;

                gcHandler.Free();
                Marshal.ZeroFreeGlobalAllocUnicode(insecureStringPointer);
            }
        }

        /// <summary>
        /// Runs DecryptSecureString with support for Action to leverage void return type
        /// </summary>
        /// <param name="secureString"></param>
        /// <param name="action"></param>
        public static void DecryptSecureString(SecureString secureString, Action<string> action)
        {
            DecryptSecureString<int>(secureString, (s) =>
            {
                action(s);
                return 0;
            });
        }
    }
}

StringsTest.cs

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security;

namespace SecurityUtils.Test
{
    [TestClass]
    public class StringsTest
    {
        [TestMethod]
        public void DecryptSecureStringWithFunc()
        {
            // Arrange
            var secureString = new SecureString();

            foreach (var c in "UserPassword123".ToCharArray())
                secureString.AppendChar(c);

            secureString.MakeReadOnly();

            // Act
            var result = Strings.DecryptSecureString<bool>(secureString, (password) =>
            {
                return password.Equals("UserPassword123");
            });

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void DecryptSecureStringWithAction()
        {
            // Arrange
            var secureString = new SecureString();

            foreach (var c in "UserPassword123".ToCharArray())
                secureString.AppendChar(c);

            secureString.MakeReadOnly();

            // Act
            var result = false;

            Strings.DecryptSecureString(secureString, (password) =>
            {
                result = password.Equals("UserPassword123");
            });

            // Assert
            Assert.IsTrue(result);
        }
    }
}

Obviously, this doesn't prevent abuse of this function in the following manner, so just be careful not to do this:

[TestMethod]
public void DecryptSecureStringWithAction()
{
    // Arrange
    var secureString = new SecureString();

    foreach (var c in "UserPassword123".ToCharArray())
        secureString.AppendChar(c);

    secureString.MakeReadOnly();

    // Act
    string copyPassword = null;

    Strings.DecryptSecureString(secureString, (password) =>
    {
        copyPassword = password; // Please don't do this!
    });

    // Assert
    Assert.IsNull(copyPassword); // Fails
}

Happy coding!

Integrand answered 12/11, 2015 at 19:42 Comment(3)
Why not use Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length); instead of the fixed section?Radiation
@sclarke81, good idea, but you'll need to use [char], not [byte].Impel
The overall approach is promising, but I don't think your attempt at pinning the managed string that contains the insecure (plain-text) copy is effective: what you're pinning instead is the original string object that you've initialized to String.Empty, not the newly allocated instance created and returned by Marshal.PtrToStringUni().Impel
R
9

I created the following extension methods based on the answer from rdev5. Pinning the managed string is important as it prevents the garbage collector from moving it around and leaving behind copies that you're unable to erase.

I think the advantage of my solution has is that no unsafe code is needed.

/// <summary>
/// Allows a decrypted secure string to be used whilst minimising the exposure of the
/// unencrypted string.
/// </summary>
/// <typeparam name="T">Generic type returned by Func delegate.</typeparam>
/// <param name="secureString">The string to decrypt.</param>
/// <param name="action">
/// Func delegate which will receive the decrypted password as a string object
/// </param>
/// <returns>Result of Func delegate</returns>
/// <remarks>
/// This method creates an empty managed string and pins it so that the garbage collector
/// cannot move it around and create copies. An unmanaged copy of the the secure string is
/// then created and copied into the managed string. The action is then called using the
/// managed string. Both the managed and unmanaged strings are then zeroed to erase their
/// contents. The managed string is unpinned so that the garbage collector can resume normal
/// behaviour and the unmanaged string is freed.
/// </remarks>
public static T UseDecryptedSecureString<T>(this SecureString secureString, Func<string, T> action)
{
    int length = secureString.Length;
    IntPtr sourceStringPointer = IntPtr.Zero;

    // Create an empty string of the correct size and pin it so that the GC can't move it around.
    string insecureString = new string('\0', length);
    var insecureStringHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

    IntPtr insecureStringPointer = insecureStringHandler.AddrOfPinnedObject();

    try
    {
        // Create an unmanaged copy of the secure string.
        sourceStringPointer = Marshal.SecureStringToBSTR(secureString);

        // Use the pointers to copy from the unmanaged to managed string.
        for (int i = 0; i < secureString.Length; i++)
        {
            short unicodeChar = Marshal.ReadInt16(sourceStringPointer, i * 2);
            Marshal.WriteInt16(insecureStringPointer, i * 2, unicodeChar);
        }

        return action(insecureString);
    }
    finally
    {
        // Zero the managed string so that the string is erased. Then unpin it to allow the
        // GC to take over.
        Marshal.Copy(new byte[length], 0, insecureStringPointer, length);
        insecureStringHandler.Free();

        // Zero and free the unmanaged string.
        Marshal.ZeroFreeBSTR(sourceStringPointer);
    }
}

/// <summary>
/// Allows a decrypted secure string to be used whilst minimising the exposure of the
/// unencrypted string.
/// </summary>
/// <param name="secureString">The string to decrypt.</param>
/// <param name="action">
/// Func delegate which will receive the decrypted password as a string object
/// </param>
/// <returns>Result of Func delegate</returns>
/// <remarks>
/// This method creates an empty managed string and pins it so that the garbage collector
/// cannot move it around and create copies. An unmanaged copy of the the secure string is
/// then created and copied into the managed string. The action is then called using the
/// managed string. Both the managed and unmanaged strings are then zeroed to erase their
/// contents. The managed string is unpinned so that the garbage collector can resume normal
/// behaviour and the unmanaged string is freed.
/// </remarks>
public static void UseDecryptedSecureString(this SecureString secureString, Action<string> action)
{
    UseDecryptedSecureString(secureString, (s) =>
    {
        action(s);
        return 0;
    });
}
Radiation answered 29/1, 2019 at 13:47 Comment(8)
While your code doesn't leak a copy of the string, it still represents a pit of despair. Nearly every operation on the System.String object will make unpinned and unerased copies. That's why this isn't built into SecureString.Cleopatracleopatre
Nice, though to zero out the entire string you'll have to use new char[length] (or multiply length with sizeof(char)).Impel
@BenVoigt: As long as the action delegate doesn't create copies of the temporary, pinned, then zeroed-out string, this approach should be as safe as or unsafe as SecureString itself - to use the latter, a plain-text representation too has to be created at some point, given that secure strings aren't OS-level constructs; the relative security comes from controlling the lifetime of that string and ensuring that it gets erased after use.Impel
@mklement0: SecureString doesn't have member functions and overloaded operators that make copies all over the place. System.String does.Cleopatracleopatre
@BenVoigt, yes, it's very easy to create accidental copies of managed strings; as as long as the action delegate doesn't do that - and that's what this hinges on - this approach should be fine; passing the string through as-is to some API that requires a plain-text password fulfills that requirement (leaving aside the issue of how careful that API is in controlling the string).Impel
@mklement0: But "passing the string through as-is to some API" can be done using the SecureString object -- p/invoke knows what to do with it. The problem is in using it like it's an ordinary managed string, because you made it look like one. Basically anything that needs to be done will, by default, get done wrong with this call signature, whether that be converting to an encoding other than UCS-2, trimming or appending data, escaping control characters, or whatever. Even calling a string comparison function could put you at risk if that function computes a normalized version!Cleopatracleopatre
@BenVoigt, not all APIs support SecureString - e.g., System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials() - and that's what this approach is for - undoubtedly, you have to be careful.Impel
@mklement0: Which is pretty darn absurd considering that it passes it to the NetworkCredential constructor which DOES accept a SecureString.Cleopatracleopatre
P
3

Final working solution according to sclarke81 solution and John Flaherty fixes is:

    public static class Utils
    {
        /// <remarks>
        /// This method creates an empty managed string and pins it so that the garbage collector
        /// cannot move it around and create copies. An unmanaged copy of the the secure string is
        /// then created and copied into the managed string. The action is then called using the
        /// managed string. Both the managed and unmanaged strings are then zeroed to erase their
        /// contents. The managed string is unpinned so that the garbage collector can resume normal
        /// behaviour and the unmanaged string is freed.
        /// </remarks>
        public static T UseDecryptedSecureString<T>(this SecureString secureString, Func<string, T> action)
        {
            int length = secureString.Length;
            IntPtr sourceStringPointer = IntPtr.Zero;

            // Create an empty string of the correct size and pin it so that the GC can't move it around.
            string insecureString = new string('\0', length);
            var insecureStringHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

            IntPtr insecureStringPointer = insecureStringHandler.AddrOfPinnedObject();

            try
            {
                // Create an unmanaged copy of the secure string.
                sourceStringPointer = Marshal.SecureStringToBSTR(secureString);

                // Use the pointers to copy from the unmanaged to managed string.
                for (int i = 0; i < secureString.Length; i++)
                {
                    short unicodeChar = Marshal.ReadInt16(sourceStringPointer, i * 2);
                    Marshal.WriteInt16(insecureStringPointer, i * 2, unicodeChar);
                }

                return action(insecureString);
            }
            finally
            {
                // Zero the managed string so that the string is erased. Then unpin it to allow the
                // GC to take over.
                Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);
                insecureStringHandler.Free();

                // Zero and free the unmanaged string.
                Marshal.ZeroFreeBSTR(sourceStringPointer);
            }
        }

        /// <summary>
        /// Allows a decrypted secure string to be used whilst minimising the exposure of the
        /// unencrypted string.
        /// </summary>
        /// <param name="secureString">The string to decrypt.</param>
        /// <param name="action">
        /// Func delegate which will receive the decrypted password as a string object
        /// </param>
        /// <returns>Result of Func delegate</returns>
        /// <remarks>
        /// This method creates an empty managed string and pins it so that the garbage collector
        /// cannot move it around and create copies. An unmanaged copy of the the secure string is
        /// then created and copied into the managed string. The action is then called using the
        /// managed string. Both the managed and unmanaged strings are then zeroed to erase their
        /// contents. The managed string is unpinned so that the garbage collector can resume normal
        /// behaviour and the unmanaged string is freed.
        /// </remarks>
        public static void UseDecryptedSecureString(this SecureString secureString, Action<string> action)
        {
            UseDecryptedSecureString(secureString, (s) =>
            {
                action(s);
                return 0;
            });
        }
    }
Pneumodynamics answered 18/4, 2020 at 7:22 Comment(0)
S
2

I derived from This answer by sclarke81. I like his answer and I'm using the derivative but sclarke81's has a bug. I don't have reputation so I can't comment. The problem seems small enough that it didn't warrant another answer and I could edit it. So I did. It got rejected. So now we have another answer.

sclarke81 I hope you see this (in finally):

Marshal.Copy(new byte[length], 0, insecureStringPointer, length);

should be:

Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);

And the full answer with the bug fix:


    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// Generic type returned by Func delegate.
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// This method creates an empty managed string and pins it so that the garbage collector
    /// cannot move it around and create copies. An unmanaged copy of the the secure string is
    /// then created and copied into the managed string. The action is then called using the
    /// managed string. Both the managed and unmanaged strings are then zeroed to erase their
    /// contents. The managed string is unpinned so that the garbage collector can resume normal
    /// behaviour and the unmanaged string is freed.
    /// 
    public static T UseDecryptedSecureString(this SecureString secureString, Func action)
    {
        int length = secureString.Length;
        IntPtr sourceStringPointer = IntPtr.Zero;

        // Create an empty string of the correct size and pin it so that the GC can't move it around.
        string insecureString = new string('\0', length);
        var insecureStringHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

        IntPtr insecureStringPointer = insecureStringHandler.AddrOfPinnedObject();

        try
        {
            // Create an unmanaged copy of the secure string.
            sourceStringPointer = Marshal.SecureStringToBSTR(secureString);

            // Use the pointers to copy from the unmanaged to managed string.
            for (int i = 0; i < secureString.Length; i++)
            {
                short unicodeChar = Marshal.ReadInt16(sourceStringPointer, i * 2);
                Marshal.WriteInt16(insecureStringPointer, i * 2, unicodeChar);
            }

            return action(insecureString);
        }
        finally
        {
            // Zero the managed string so that the string is erased. Then unpin it to allow the
            // GC to take over.
            Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);
            insecureStringHandler.Free();

            // Zero and free the unmanaged string.
            Marshal.ZeroFreeBSTR(sourceStringPointer);
        }
    }

    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// This method creates an empty managed string and pins it so that the garbage collector
    /// cannot move it around and create copies. An unmanaged copy of the the secure string is
    /// then created and copied into the managed string. The action is then called using the
    /// managed string. Both the managed and unmanaged strings are then zeroed to erase their
    /// contents. The managed string is unpinned so that the garbage collector can resume normal
    /// behaviour and the unmanaged string is freed.
    /// 
    public static void UseDecryptedSecureString(this SecureString secureString, Action action)
    {
        UseDecryptedSecureString(secureString, (s) =>
        {
            action(s);
            return 0;
        });
    }
}
Shanklin answered 23/10, 2019 at 23:5 Comment(1)
Good point; I've left a comment on the referenced answer, which should notify the OP.Impel
H
2

The code accepted as the answer is correct, and will work in most circumstances, but as mentioned in the comments using BSTR would be better and will cover all circumstances:

private string SecureStringToString(SecureString value) {
    IntPtr valuePtr = IntPtr.Zero;
    try {
        valuePtr = Marshal.SecureStringToBSTR(value);
        return Marshal.PtrToStringBSTR(valuePtr);
    } finally {
        Marshal.ZeroFreeBSTR(valuePtr);
    }
}
Hypersonic answered 25/9, 2021 at 15:3 Comment(0)
C
2

Use the following:

var plaintextPwd = new System.Net.NetworkCredential("", <securestring with your encrypted password>).Password
Celia answered 18/2, 2022 at 15:32 Comment(2)
to me I think this is the best way you don't have to go about defining new functions or extensions or any of that other overhead. especially nice when you only need to do this once and would rather not have to add a ton of code.Mathur
Other answers discuss important concepts regarding zeroing and pinning memory, etc. - and I would highly recommend developers read those answers and understand those issues (and then realize some dotnet http APIs subvert your attempts to keep a plaintext password string from being copied or resident in memory long-term anyways) I note the OP's actual question was "How can I convert an ordinary System.Security.SecureString to System.String?" Thus this is the correct answer.Klutz
C
1

This C# code is what you want.

%ProjectPath%/SecureStringsEasy.cs

using System;
using System.Security;
using System.Runtime.InteropServices;
namespace SecureStringsEasy
{
    public static class MyExtensions
    {
        public static SecureString ToSecureString(string input)
        {
            SecureString secureString = new SecureString();
            foreach (var item in input)
            {
                secureString.AppendChar(item);
            }
            return secureString;
        }
        public static string ToNormalString(SecureString input)
        {
            IntPtr strptr = Marshal.SecureStringToBSTR(input);
            string normal = Marshal.PtrToStringBSTR(strptr);
            Marshal.ZeroFreeBSTR(strptr);
            return normal;
        }
    }
}
Cotterell answered 27/6, 2019 at 23:57 Comment(1)
I'm not sure if you intended it, but the arguments need to have "this" prefixed to be proper extension methodsMatelote
C
-5
// using so that Marshal doesn't have to be qualified
using System.Runtime.InteropServices;    
//using for SecureString
using System.Security;
public string DecodeSecureString (SecureString Convert) 
{
    //convert to IntPtr using Marshal
    IntPtr cvttmpst = Marshal.SecureStringToBSTR(Convert);
    //convert to string using Marshal
    string cvtPlainPassword = Marshal.PtrToStringAuto(cvttmpst);
    //return the now plain string
    return cvtPlainPassword;
}
Courageous answered 13/1, 2015 at 16:12 Comment(4)
This answer has a memory leak.Cleopatracleopatre
@BenVoigt Can you explain further please how this has a memory leak?Ciccia
@ElRonnoco: Nothing frees the BSTR explicitly, and it's not a .NET object so the garbage collector doesn't take care of it either. Compare to https://mcmap.net/q/57422/-how-to-convert-securestring-to-system-string which was posted 5 years earlier and doesn't leak.Cleopatracleopatre
This answer does not work on non windows platforms. PtrToStringAuto is wrong for an explanation see: github.com/PowerShell/PowerShell/issues/…Kicksorter
M
-5

If you use a StringBuilder instead of a string, you can overwrite the actual value in memory when you are done. That way the password won't hang around in memory until garbage collection picks it up.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());
Mccloud answered 12/3, 2016 at 12:56 Comment(2)
While this is true, the garbage collector may still move the StringBuilder buffer around in memory during generational compaction, which makes the "overwrite the actual value" fail, because there is another (or more) leftover copy that isn't destroyed.Cleopatracleopatre
This doesn't even remotely answer the question.Chantel

© 2022 - 2024 — McMap. All rights reserved.