How to convert SecureString to System.String?

2020-01-24 19:09发布

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.

11条回答
我想做一个坏孩纸
2楼-- · 2020-01-24 19:34

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);
    }
}
查看更多
男人必须洒脱
3楼-- · 2020-01-24 19:40
// 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;
}
查看更多
叛逆
4楼-- · 2020-01-24 19:41

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;
        }
    }
}
查看更多
\"骚年 ilove
5楼-- · 2020-01-24 19:41

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());
查看更多
Fickle 薄情
6楼-- · 2020-01-24 19:50

Using some the examples above I made it more obvious for my case instead of a func delegate callback, of course it's up to the developer to dispose.

 public class SecureStringContext : IDisposable
{
    #region fields
    private GCHandle? _gcHandler = null;
    private string _insecureString = null;
    private IntPtr? _insecureStringPointer = null;
    private SecureString _secureString = null;      
    #endregion

    #region ctor
    public SecureStringContext(SecureString secureString)
    {
        _secureString = secureString;
        _secureString.MakeReadOnly();
        DecryptSecureString();

    }
    #endregion

    #region methos
    /// <summary>
    /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
    /// </summary>
    private string DecryptSecureString()
    {
        _insecureStringPointer = IntPtr.Zero;
        _insecureString = String.Empty;
        _gcHandler = GCHandle.Alloc(_insecureString, GCHandleType.Pinned);

        _insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(_secureString);
        _insecureString = Marshal.PtrToStringUni(_insecureStringPointer.GetValueOrDefault(IntPtr.Zero));

        return _insecureString;
    }

    private void WipeInsecureString()
    {
        //clear memory immediately - don't wait for garbage collector
        unsafe
        {
            fixed (char* ptr = _insecureString)
            {
                for (int i = 0; i < _insecureString.Length; i++)
                {
                    ptr[i] = '\0';
                }
            }
        }
        _insecureString = null;
    }
    #endregion

    #region properties
    public string InsecureString { get => _insecureString; }
    #endregion

    #region dispose
    public void Dispose()
    {
        //clear memory immediately - don't wait for garbage collector
        WipeInsecureString();
    }
    #endregion
}

Usage (keep in mind once disposed the reference will be also.)

using (var secureStringContext = new SecureStringContext(FabricSettingsHelper.GetConnectionSecureString()))
{
   //this is the clear text connection string
   x.UseSqlServerStorage(secureStringContext.InsecureString);
} //disposed clear text is removed from memory
查看更多
Melony?
7楼-- · 2020-01-24 19:50

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;
        });
    }
}
查看更多
登录 后发表回答