Как преобразовать SecureString в System.String?

Все оговорки об отсутствии безопасности SecureString, создавая System.String из него в сторону, как это сделать?

Как преобразовать обычную System.Security.SecureString в System.String?

Я уверен, что многие из вас, знакомые с SecureString, ответят, что никогда не следует преобразовывать SecureString в обычную строку .NET, поскольку она удаляет все защитные меры безопасности. Я знаю. Но сейчас моя программа все равно работает с обычными строками, и я пытаюсь повысить ее безопасность, и хотя я собираюсь использовать API, который возвращает мне SecureString, я не пытаюсь использовать это для повышения моей безопасности.

Я знаю Marshal.SecureStringToBSTR, но я не знаю, как взять этот BSTR и сделать из него System.String.

Для тех, кто может потребовать узнать, почему я когда-либо хотел бы это сделать, хорошо, я беру пароль у пользователя и отправляю его как POST-форму html для входа пользователя на веб-сайт. Итак... это действительно нужно делать с управляемыми, незашифрованными буферами. Если бы я мог получить доступ к неуправляемому, незашифрованному буферу, я предполагаю, что могу писать побайтовые потоки в сетевом потоке и надеяться, что это защитит пароль полностью. Я надеюсь получить хотя бы один из этих сценариев.

Ответ 1

Используйте System.Runtime.InteropServices.Marshal класс:

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

Если вы хотите избежать создания управляемого строкового объекта, вы можете получить доступ к необработанным данным с помощью 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);
  }
}

Ответ 2

Очевидно, вы знаете, как это побеждает всю цель SecureString, но я все равно его повторю.

Если вы хотите использовать один лайнер, попробуйте это: (только .NET 4 и выше)

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

Где securePassword является SecureString.

Ответ 3

Dang. сразу после публикации этого ответа я нашел ответ в в этой статье. Но если кто-нибудь знает, как получить доступ к неуправляемому незашифрованному буферу IntPtr, который предоставляет этот метод, по одному байту за один раз, так что мне не нужно создавать управляемый объект строки из него, чтобы поддерживать высокую безопасность, добавьте ответ.:)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}

Ответ 4

Я думаю, что для зависимых функций SecureString было бы лучше инкапсулировать свою зависимую логику в анонимную функцию для лучшего контроля за дешифрованной строкой в памяти (когда-то закрепленной).

Реализация для расшифровки SecureStrings в этом фрагменте будет:

  1. Закрепите строку в памяти (это то, что вы хотите сделать, но, похоже, отсутствует в большинстве ответов здесь).
  2. Передайте свою ссылку делегату Func/Action.
  3. Очистите его от памяти и отпустите GC в блоке finally.

Это, очевидно, значительно упрощает "стандартизацию" и поддержку вызывающих абонентов по сравнению с использованием менее желательных альтернатив:

  • Возвращение расшифрованной строки из вспомогательной функции string DecryptSecureString(...).
  • Дублирование этого кода везде, где это необходимо.

Обратите внимание, у вас есть два варианта:

  1. static T DecryptSecureString<T> который позволяет получить доступ к результату делегата Func от вызывающей стороны (как показано в DecryptSecureStringWithFunc теста DecryptSecureStringWithFunc).
  2. static void DecryptSecureString - это просто "void" версия, в которой используется делегат Action в тех случаях, когда вы фактически не хотите/не должны что-либо возвращать (как продемонстрировано в тестовом методе DecryptSecureStringWithAction).

Пример использования для обоих можно найти в StringsTest классе StringsTest.

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);
        }
    }
}

Очевидно, что это не предотвращает злоупотребление этой функцией следующим образом, поэтому будьте осторожны, чтобы этого не делать:

[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
}

Удачного кодирования!

Ответ 5

На мой взгляд, методы расширения - наиболее удобный способ решить эту проблему.

Я взял Стив в CO отличный ответ и поместил его в класс расширения следующим образом, вместе с второй метод, который я добавил для поддержки другого направления (строка → защищенная строка), поэтому вы можете создать защищенную строку и впоследствии преобразовать ее в обычную строку:

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;
    }
}

Теперь вы можете просто преобразовать свои строки назад и вперед следующим образом:

// create a secure string
System.Security.SecureString securePassword = "MyCleverPwd123".ToSecureString(); 
// convert it back to plain text
String plainPassword = securePassword.ToPlainString();  // convert back to normal string

Но имейте в виду, что метод декодирования должен использоваться только для тестирования.

Ответ 6

Я создал следующие методы расширения на основе ответа от rdev5. Прикрепление управляемой строки важно, поскольку она не позволяет сборщику мусора перемещать ее и оставлять копии, которые вы не можете стереть.

Я думаю, что преимущество моего решения состоит в том, что небезопасный код не нужен.

/// <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;
    });
}

Ответ 7

Этот код С# - то, что вы хотите.

%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;
        }
    }
}

Ответ 8

Используя несколько приведенных выше примеров, я сделал это более очевидным для моего случая вместо обратного вызова делегата func, разумеется, разработчик может его утилизировать.

 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
}

Использование (имейте в виду, что после утилизации ссылка также будет.)

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

Ответ 9

в настоящее время вы можете просто использовать

Securestring spwd  = ....
Char[] unsecure = spwd.ToInsecureArray()

Ответ 10

// 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;
}

Ответ 11

Если вы используете StringBuilder вместо string, вы можете перезаписать фактическое значение в памяти, когда вы закончите. Таким образом, пароль не будет зависеть в памяти до тех пор, пока сбор мусора не подберет его.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());