How to convert SecureString to System.String?

后端 未结 11 744
孤独总比滥情好
孤独总比滥情好 2020-11-27 10:22

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 S

相关标签:
11条回答
  • 2020-11-27 11:02

    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());
    
    0 讨论(0)
  • 2020-11-27 11:10

    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;
                });
            }
        }
    
    0 讨论(0)
  • 2020-11-27 11:18

    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.

    0 讨论(0)
  • 2020-11-27 11:20

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-27 11:20

    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!

    0 讨论(0)
提交回复
热议问题