2009-05-04 22 views
122

Tutte le prenotazioni sulla non protezione di SecureString mediante la creazione di un System.String di esso a parte, come può essere eseguito?Come convertire SecureString in System.String?

Come posso convertire un ordinario System.Security.SecureString in System.String?

Sono sicuro che molti di voi che hanno familiarità con SecureString risponderanno che non si dovrebbe mai trasformare un SecureString in una normale stringa .NET perché rimuove tutte le protezioni di sicurezza. Lo so. Ma in questo momento il mio programma fa tutto con le stringhe ordinarie comunque, e sto cercando di migliorare la sua sicurezza e anche se userò un'API che restituisce una SecureString per me sono non cercando di usarlo per aumentare il mio sicurezza.

Sono a conoscenza di Marshal.SecureStringToBSTR, ma non so come prendere quel BSTR e creare un System.String.

Per coloro che potrebbero chiedere di sapere perché vorrei farlo, beh, sto prendendo una password da un utente e la invio come un modulo HTML per registrare l'utente in un sito web. Quindi ... questo deve essere fatto con buffer gestiti, non crittografati. Se potessi anche ottenere l'accesso al buffer non gestito, non crittografato, immagino di poter scrivere byte in streaming byte per byte sullo stream della rete e spero che mantenga la password sicura per l'intero percorso. Sto sperando in una risposta ad almeno uno di questi scenari.

risposta

152

utilizzare la classe 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); 
    } 
} 

Se si vuole evitare la creazione di un oggetto stringa gestito, è possibile accedere ai dati grezzi usando 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

Ho avuto il mio voto anche anni dopo, grazie per l'aiuto! Solo una breve nota: funziona anche come statico, nella sua memoria. –

+0

Ho usato 'StopWatch' e' SecureStringToString' ha impiegato 4,6 secondi per l'esecuzione. È lento per me. Qualcuno ha lo stesso tempo o qualcosa di più veloce? – radbyx

+0

@radbyx In una configurazione di prova rapida e sporca, posso chiamarla 1000 volte in 76 ms. La prima invocazione richiede 0,3 ms e le successive invocazioni ~ 0,07 ms. Quanto è grande la tua stringa sicura e quale versione del framework stai usando? –

42

Dang. right dopo aver postato questo ho trovato la risposta in profondità this article. Ma se qualcuno sa come accedere al buffer non gestito, non crittografato, IntPtr, che questo metodo espone, un byte alla volta in modo che non debba creare un oggetto stringa gestita al di fuori di esso per mantenere alta la sicurezza, si prega di aggiungere una risposta. :)

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

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

Si può certamente usare la parola chiave 'unsafe' e un' char * ', basta chiamare' bstr.ToPointer() 'e trasmettere. –

61

Ovviamente si sapere come questo sconfigge l'intero scopo di un SecureString, ma lo rifarò comunque.

Se si desidera un one-liner, provate questo: (.NET 4 e superiori solo)

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

Dove securePassword è un SecureString.

+6

Anche se non riesce a risolvere lo scopo della produzione, la soluzione è perfetta per i test unitari. Grazie. – beterthanlife

+0

Questo mi ha aiutato a capire che un SecureString (System.Security.SecureString) non veniva passato al mio ApiController (webapi). Thx – granadaCoder

+2

Nota in PowerShell questo è '[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

Questa risposta ha una perdita di memoria. –

+0

@BenVoigt Puoi spiegare ulteriormente per favore come questo ha una perdita di memoria? –

+2

@ElRonnoco: Niente libera il 'BSTR' esplicitamente, e non è un oggetto .NET, quindi il garbage collector non si occupa di esso. Confronta con http://stackoverflow.com/a/818709/103167 che è stato pubblicato 5 anni prima e non perde. –

9

ritengo che sarebbe meglio per SecureString funzioni dipendenti per incapsulare loro logica dipendente in una funzione anonima per un migliore controllo della stringa decodificata nella memoria (una volta riposte).

L'implementazione per decifrare SecureStrings in questo frammento sarà:

  1. Pin la stringa in memoria (che è quello che vuole fare, ma sembra mancare dalla maggior parte delle risposte qui).
  2. Passa its reference al delegato Func/Action.
  3. Scrub dalla memoria e rilascia il GC nel blocco finally.

Questo rende ovviamente molto più facile da "standardizzare" e mantenere i chiamanti contro basandosi su alternative meno desiderabili:

  • Tornando la stringa decifrata da una funzione string DecryptSecureString(...) aiutante.
  • Duplicare questo codice ovunque sia necessario.

Avviso qui, avete due opzioni:

  1. static T DecryptSecureString<T> che consente di accedere al risultato del Func delegato del chiamante (come mostrato nel metodo DecryptSecureStringWithFunc di prova).
  2. static void DecryptSecureString è semplicemente una versione "vuota" che impiega un delegato Action nei casi in cui effettivamente non si desidera/deve restituire nulla (come dimostrato nel metodo di prova DecryptSecureStringWithAction).

L'utilizzo di esempio per entrambi può essere trovato nella classe StringsTest inclusa.

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

Ovviamente, ciò non impedisce l'abuso di questa funzione nel modo seguente, quindi basta fare attenzione a non A fare questo:

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

Felice codifica!

-3

Se si utilizza uno StringBuilder anziché uno string, al termine è possibile sovrascrivere il valore effettivo in memoria. In questo modo la password non rimarrà in memoria fino a quando la garbage collection non lo preleverà.

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

Mentre questo è vero, il garbage collector può ancora spostare il buffer StringBuilder in giro durante la compattazione generazionale, il che rende fallito il "sovrascrivere il valore attuale", perché c'è un'altra (o più) copia rimanente che non viene distrutta . –

+3

Questo non consente nemmeno di rispondere in remoto alla domanda. –

5

A mio parere, metodi di estensione sono il modo più comodo per risolvere questo problema.

Ho preso Steve in CO'sexcellent answer e lo ho inserito in una classe di estensione come segue, insieme a un secondo metodo che ho aggiunto per supportare l'altra direzione (stringa -> stringa sicura), in modo da poter creare una stringa sicura e convertirla in una stringa normale dopo:

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

Con questo, è ora possibile semplicemente convertire le stringhe avanti e indietro in questo modo:

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

Ma tenere in mente il metodo di decodifica dovrebbe essere usato solo per i test.

Problemi correlati