Как преобразовать SecureString в System.String? - PullRequest
137 голосов
/ 04 мая 2009

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

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

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

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

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

Ответы [ 8 ]

175 голосов
/ 04 мая 2009

Используйте класс 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);
  }
}
89 голосов
/ 09 сентября 2014

Очевидно, вы знаете, как это разрушает всю цель SecureString, но я все равно переформулирую.

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

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

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

44 голосов
/ 04 мая 2009

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

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
13 голосов
/ 12 ноября 2015

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

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

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

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

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

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

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

Пример использования для обоих можно найти в включенном классе 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
}

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

9 голосов
/ 24 июня 2016

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

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

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

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

4 голосов
/ 29 января 2019

Я создал следующие методы расширения на основе ответа от 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;
    });
}
0 голосов
/ 12 марта 2016

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

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());
0 голосов
/ 13 января 2015
// 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...