2009-05-04 4 views
122

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

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

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

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

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

ответ

152

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

Получил мое голосование слишком много лет спустя, спасибо за помощь! Простое замечание: это также работает как статичное, в собственной памяти. –

+0

Я использовал 'StopWatch', а' SecureStringToString' занял 4.6sec для запуска. Мне мешает. Кто-нибудь получает то же время или что-то быстрее? – radbyx

+0

@radbyx В быстрой и грязной тестовой настройке я могу назвать ее 1000 раз в 76 мс. Первый вызов занимает 0,3 мс и последующие вызовы ~ 0.07мс. Насколько велика ваша безопасная строка и какая версия фреймворка вы используете? –

42

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

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

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

Вы можете использовать ключевое слово 'unsafe' и' char * ', просто назовите' bstr.ToPointer() 'и cast. –

61

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

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

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

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

+6

Несмотря на то, что это поражает цель производства, ваше решение идеально подходит для модульных тестов. Благодарю. – beterthanlife

+0

Это помогло мне разобраться, что SecureString (System.Security.SecureString) не передавался моему ApiController (webapi). Thx – granadaCoder

+2

Примечание в PowerShell это '[System.Net.NetworkCredential] :: new ('', $ securePassword) .Password' – stijn

-3
// 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; 
} 
+0

Этот ответ имеет утечку памяти. –

+0

@BenVoigt Можете ли вы объяснить, пожалуйста, как это происходит при утечке памяти? –

+2

@ElRonnoco: Ничто не освобождает 'BSTR' явно, и это не объект .NET, поэтому сборщик мусора также не заботится об этом. Сравните с http://stackoverflow.com/a/818709/103167, который был опубликован 5 лет назад и не просачивается. –

9

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

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

  1. Pin строка в памяти (это то, что вы хотите сделать, но, кажется, отсутствует в большинстве ответов здесь).
  2. Передача its reference делегату Func/Action.
  3. Скраб его из памяти и отпустите GC в блоке 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 
      { 
       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 
} 

Счастливое кодирование!

-3

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

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

Хотя это правда, сборщик мусора может по-прежнему перемещать буфер StringBuilder в памяти во время уплотнения поколений, что приводит к сбою «перезаписывания фактического значения», потому что есть другая (или более) оставшаяся копия, которая не разрушена , –

+3

Это даже не отдаленно отвечает на вопрос. –

5

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

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

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 

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

Смежные вопросы