Jak przekonwertować SecureString na System.String?

156

Wszelkie zastrzeżenia dotyczące zabezpieczenia SecureString poprzez utworzenie Systemu. Pomijając to , jak można to zrobić?

Jak przekonwertować zwykły System.Security.SecureString na System.String?

Jestem pewien, że wielu z was, którzy są zaznajomieni z SecureString, odpowie, że nigdy nie należy przekształcać SecureString w zwykły ciąg .NET, ponieważ usuwa on wszystkie zabezpieczenia. Wiem . Ale w tej chwili mój program i tak robi wszystko za pomocą zwykłych ciągów znaków i próbuję zwiększyć jego bezpieczeństwo i chociaż zamierzam używać interfejsu API, który zwraca do mnie SecureString, nie próbuję go używać do zwiększenia mojego bezpieczeństwa.

Znam Marshal.SecureStringToBSTR, ale nie wiem, jak wziąć ten BSTR i utworzyć z niego System.String.

Dla tych, którzy mogą chcieć wiedzieć, dlaczego kiedykolwiek chciałbym to zrobić, cóż, biorę hasło od użytkownika i przesyłam je jako formularz HTML POST, aby zalogować użytkownika na stronie internetowej. Więc ... to naprawdę musi być zrobione z zarządzanymi, niezaszyfrowanymi buforami. Gdybym mógł nawet uzyskać dostęp do niezarządzanego, niezaszyfrowanego bufora, wyobrażam sobie, że mógłbym zapisywać strumień bajt po bajcie w strumieniu sieciowym i mieć nadzieję, że dzięki temu hasło będzie bezpieczne przez całą drogę. Liczę na odpowiedź na przynajmniej jeden z tych scenariuszy.

Andrew Arnott
źródło

Odpowiedzi:

192

Skorzystaj z System.Runtime.InteropServices.Marshalzajęć:

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

Jeśli chcesz uniknąć tworzenia zarządzanego obiektu ciągu, możesz uzyskać dostęp do surowych danych za pomocą 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);
  }
}
Rasmus Faber
źródło
1
Również po latach otrzymałem pozytywny głos, dzięki za pomoc! Krótka uwaga: to działa również jako statyczne, we własnej pamięci.
John Suit
Użyłem StopWatchi SecureStringToStringzajęło mi to 4,6 sekundy. Dla mnie jest za wolno. Czy ktoś ma ten sam czas czy coś szybciej?
radbyx
@radbyx W szybkiej i brudnej konfiguracji testowej mogę to nazwać 1000 razy w 76 ms. Pierwsze wywołanie trwa 0,3 ms, a kolejne ~ 0,07 ms. Jak duży jest twój bezpieczny ciąg i której wersji frameworka używasz?
Rasmus Faber
Długość mojego SecureString to 168. Używam .NET Framework 3.5, jeśli to odpowiedział na twoje pytanie? Próbowałem 5-10 razy, to zawsze około 4,5-4,65 sekundy ~ Chciałbym mieć twój czas
radbyx
@RasmusFaber Mój błąd, dodałem a Database.GetConnectionString()do twojego kodu, aby uzyskać bezpieczny ciąg, który był złą częścią, która zajęła prawie 5 sekund (i tak, powinienem się temu przyjrzeć! :) Twój kod zajął .00 miliardów sekund w moim stoperze, więc jest wszystko dobrze. Dzięki za wskazanie mi właściwego kierunku.
radbyx
108

Oczywiście wiesz, jak to podważa cały cel SecureString, ale i tak to powtórzę.

Jeśli chcesz mieć jedną linijkę, spróbuj tego: (tylko .NET 4 i nowsze)

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

Gdzie securePassword to SecureString.

Steve w CO
źródło
10
Chociaż rozwiązuje to cel w produkcji, Twoje rozwiązanie jest idealne do testów jednostkowych. Dzięki.
beterthanlife
Pomogło mi to zorientować się, że SecureString (System.Security.SecureString) nie jest przekazywany do mojego ApiController (webapi). Dzięki
granadaCoder
5
Zauważ, że w PowerShell to jest[System.Net.NetworkCredential]::new('', $securePassword).Password
stijn
1
@ TheIncorriable1 czy możesz to rozwinąć? Np. Kiedy ''nie jest tego samego typu co [String]::Empty? Również New-Object Net.Credentialnie działa dla mnie: nie można znaleźć typu [Net.Credential]: sprawdź, czy zestaw zawierający ten typ jest załadowany
stijn
2
Niweczy cel SecureString, ponieważ tworzy niezaszyfrowaną kopię zawartości SecureString w zwykłym ciągu. Za każdym razem, gdy to robisz, dodajesz co najmniej jedną (i przy Garbage Collection być może wiele) kopii niezaszyfrowanego ciągu do pamięci. Jest to uważane za ryzyko dla niektórych aplikacji wrażliwych na bezpieczeństwo, a SecureString został wdrożony specjalnie w celu zmniejszenia ryzyka.
Steve In CO
49

Cholera. zaraz po opublikowaniu tego znalazłem odpowiedź głęboko w tym artykule . Ale jeśli ktoś wie, jak uzyskać dostęp do niezarządzanego, niezaszyfrowanego bufora IntPtr, który ta metoda ujawnia, po jednym bajcie na raz, aby nie musieć tworzyć z niego zarządzanego obiektu ciągu, aby utrzymać wysokie bezpieczeństwo, dodaj odpowiedź. :)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
Andrew Arnott
źródło
Z pewnością możesz użyć unsafesłowa kluczowego i a char*, po prostu zadzwoń bstr.ToPointer()i rzuć.
Ben Voigt,
@BenVoigt BSTR ma terminator o wartości null po danych ciągu ze względów bezpieczeństwa, ale umożliwia również osadzanie znaków o wartości null w ciągu. Więc jest to trochę bardziej skomplikowane, musisz także pobrać przedrostek długości, który znajduje się przed tym wskaźnikiem. docs.microsoft.com/en-us/previous-versions/windows/desktop/ ...
Wim Coenen
@WimCoenen: Prawda, ale nieważna. Długość przechowywana w BSTR będzie kopią długości już dostępnej z SecureString.Length.
Ben Voigt
@BenVoigt ah, mój błąd. Myślałem, że SecureString nie ujawnił żadnych informacji o ciągu.
Wim Coenen
@WimCoenen: SecureStringnie próbuje ukryć wartości, stara się zapobiec tworzeniu kopii wartości w regionach, których nie można niezawodnie nadpisać, takich jak pamięć zebrana bezużyteczna, plik stronicowania, itp. Celem jest, aby po zakończeniu SecureStringokresu życia absolutnie żadna kopia tajemnicy nie pozostaje w pamięci. Nie zapobiega tworzeniu i wyciekaniu kopii, ale nigdy tak nie jest.
Ben Voigt
15

Moim zdaniem metody rozszerzające są najwygodniejszym sposobem rozwiązania tego problemu.

Wziąłem Steve'a w doskonałej odpowiedzi CO i umieściłem go w klasie rozszerzającej w następujący sposób, wraz z drugą metodą, którą dodałem, aby obsługiwać również drugi kierunek (ciąg -> bezpieczny ciąg), dzięki czemu możesz utworzyć bezpieczny ciąg i przekonwertować go na potem normalny ciąg:

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

Dzięki temu możesz teraz po prostu konwertować ciągi znaków w tę iz powrotem w następujący sposób:

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

Należy jednak pamiętać, że metoda dekodowania powinna być używana tylko do testowania.

Matt
źródło
14

Myślę, że najlepiej byłoby, gdyby SecureStringfunkcje zależne hermetyzowały swoją zależną logikę w funkcji anonimowej, aby uzyskać lepszą kontrolę nad odszyfrowanym ciągiem w pamięci (po przypięciu).

Implementacja do odszyfrowywania SecureStrings w tym fragmencie:

  1. Przypnij ciąg w pamięci (co chcesz zrobić, ale wydaje się, że brakuje go w większości odpowiedzi tutaj).
  2. Przekaż jej odwołanie do delegata Func / Action.
  3. Wyszoruj go z pamięci i zwolnij GC w finallybloku.

To oczywiście znacznie ułatwia „standaryzację” i utrzymanie dzwoniących w porównaniu z poleganiem na mniej pożądanych alternatywach:

  • Zwracanie odszyfrowanego ciągu z string DecryptSecureString(...)funkcji pomocniczej.
  • Kopiowanie tego kodu wszędzie tam, gdzie jest to potrzebne.

Zauważ, że masz dwie opcje:

  1. static T DecryptSecureString<T>który umożliwia dostęp do wyniku Funcdelegata z wywołującego (jak pokazano w DecryptSecureStringWithFuncmetodzie testowej).
  2. static void DecryptSecureStringjest po prostu wersją "void", która zatrudnia Actiondelegata w przypadkach, gdy faktycznie nie chcesz / nie musisz niczego zwracać (jak pokazano w DecryptSecureStringWithActionmetodzie testowej).

Przykładowe użycie dla obu można znaleźć w StringsTestdołączonej klasie.

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

Oczywiście nie zapobiega to nadużywaniu tej funkcji w następujący sposób, więc po prostu uważaj, aby tego nie robić:

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

Miłego kodowania!

Matt Borja
źródło
Dlaczego nie użyć Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);zamiast fixedsekcji?
sclarke81
@ sclarke81, dobry pomysł, ale trzeba do użytku [char], nie [byte].
mklement0
1
Ogólne podejście jest obiecujące, ale nie sądzę, aby Twoja próba przypięcia zarządzanego ciągu zawierającego niezabezpieczoną kopię (zwykły tekst) była skuteczna: zamiast tego przypinasz oryginalny obiekt ciągu, który został zainicjowany String.Empty, a nie nowo przydzielona instancja utworzona i zwrócona przez Marshal.PtrToStringUni().
mklement0
7

Stworzyłem następujące metody rozszerzające na podstawie odpowiedzi z rdev5 . Przypięcie zarządzanego ciągu jest ważne, ponieważ zapobiega przenoszeniu go przez moduł wyrzucania elementów bezużytecznych i pozostawianiu kopii, których nie można usunąć.

Myślę, że zaletą mojego rozwiązania jest to, że nie jest potrzebny żaden niebezpieczny kod.

/// <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;
    });
}
sclarke81
źródło
Chociaż twój kod nie wycieka kopii łańcucha, nadal reprezentuje dołek rozpaczy . Prawie każda operacja na System.Stringobiekcie spowoduje utworzenie odpiętych i nieskasowanych kopii. Dlatego nie jest to wbudowane SecureString.
Ben Voigt
Fajnie, chociaż wyzerować cały ciąg, którego będziesz musiał użyć new char[length](lub pomnożyć lengthprzez sizeof(char)).
mklement0
@BenVoigt: Dopóki actiondelegat nie utworzy kopii tymczasowego, przypiętego, a następnie zerowanego ciągu, to podejście powinno być równie bezpieczne lub niebezpieczne jak SecureStringsamo - aby użyć tego drugiego, reprezentacja w postaci zwykłego tekstu również musi zostać utworzone w pewnym momencie, biorąc pod uwagę, że bezpieczne łańcuchy nie są konstrukcjami na poziomie systemu operacyjnego; względne bezpieczeństwo wynika z kontrolowania żywotności tego ciągu i zapewnienia, że ​​zostanie on usunięty po użyciu.
mklement0
@ mklement0: SecureStringnie ma funkcji składowych i przeciążonych operatorów, które tworzą kopie w każdym miejscu. System.Stringrobi.
Ben Voigt,
1
@ mklement0: Co jest cholernie absurdalne, biorąc pod uwagę, że przekazuje go NetworkCredentialkonstruktorowi, który akceptuje plik SecureString.
Ben Voigt
0

Ten kod C # jest tym, czego chcesz.

%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;
        }
    }
}
Eric Alexander Silveira
źródło
0

Wyprowadziłem z tej odpowiedzi przez sclarke81 . Podoba mi się jego odpowiedź i używam pochodnej, ale sclarke81 ma błąd. Nie mam reputacji, więc nie mogę komentować. Problem wydaje się na tyle mały, że nie wymagał innej odpowiedzi i mogłem go edytować. Więc zrobiłem. Został odrzucony. Więc teraz mamy inną odpowiedź.

sclarke81 Mam nadzieję, że zobaczysz to (w końcu):

Marshal.Copy(new byte[length], 0, insecureStringPointer, length);

Powinien być:

Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);

I pełna odpowiedź z poprawką błędu:


    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// Generic type returned by Func delegate.
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// 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.
    /// 
    public static T UseDecryptedSecureString(this SecureString secureString, Func 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 * 2], 0, insecureStringPointer, length * 2);
            insecureStringHandler.Free();

            // Zero and free the unmanaged string.
            Marshal.ZeroFreeBSTR(sourceStringPointer);
        }
    }

    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// 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.
    /// 
    public static void UseDecryptedSecureString(this SecureString secureString, Action action)
    {
        UseDecryptedSecureString(secureString, (s) =>
        {
            action(s);
            return 0;
        });
    }
}
John Flaherty
źródło
Słuszna uwaga; Zostawiłem komentarz do przywoływanej odpowiedzi, który powinien powiadomić OP.
mklement0
0

Ostateczne rozwiązanie robocze zgodne z rozwiązaniem sclarke81 i poprawkami Johna Flaherty'ego to:

    public static class Utils
    {
        /// <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 * 2], 0, insecureStringPointer, length * 2);
                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;
            });
        }
    }
Rustam Shafigullin
źródło
-5
// 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;
}
Jesse Motes
źródło
Ta odpowiedź zawiera wyciek pamięci.
Ben Voigt
@BenVoigt Czy możesz dalej wyjaśnić, w jaki sposób doszło do wycieku pamięci?
El Ronnoco,
4
@ElRonnoco: Nic nie zwalnia BSTRjawnie i nie jest to obiekt .NET, więc garbage collector też się tym nie zajmuje. Porównaj ze stackoverflow.com/a/818709/103167, który został opublikowany 5 lat wcześniej i nie wycieka.
Ben Voigt,
Ta odpowiedź nie działa na platformach innych niż Windows. PtrToStringAuto jest błędne dla wyjaśnienia, patrz: github.com/PowerShell/PowerShell/issues/ ...
K. Frank
-5

Jeśli użyjesz a StringBuilderzamiast a string, możesz nadpisać rzeczywistą wartość w pamięci, kiedy skończysz. W ten sposób hasło nie będzie wisiało w pamięci, dopóki nie zostanie odebrane przez funkcję czyszczenia pamięci.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());
Michael Liben
źródło
2
Chociaż jest to prawdą, moduł odśmiecania pamięci może nadal przenosić bufor StringBuilder w pamięci podczas kompresji pokoleniowej, co powoduje niepowodzenie „zastąpienia rzeczywistej wartości”, ponieważ istnieje inna (lub więcej) pozostałej kopii, która nie jest zniszczona.
Ben Voigt,
4
To nawet nie odpowiada na pytanie.
Jay Sullivan