How to convert SecureString to System.String?

后端 未结 11 816
孤独总比滥情好
孤独总比滥情好 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: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 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
        {
            /// 
            /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
            /// 
            /// Generic type returned by Func delegate
            /// Func delegate which will receive the decrypted password pinned in memory as a String object
            /// Result of Func delegate
            public static T DecryptSecureString(SecureString secureString, Func 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);
                }
            }
    
            /// 
            /// Runs DecryptSecureString with support for Action to leverage void return type
            /// 
            /// 
            /// 
            public static void DecryptSecureString(SecureString secureString, Action action)
            {
                DecryptSecureString(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(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!

提交回复
热议问题