Wie konvertiere ich SecureString in System.String?

155

Alle Reservierungen über Entsichern Ihre Secure durch einen System.String aus ihm zu schaffen beiseite , wie es getan werden kann?

Wie kann ich einen normalen System.Security.SecureString in System.String konvertieren?

Ich bin sicher, dass viele von Ihnen, die mit SecureString vertraut sind, antworten werden, dass man einen SecureString niemals in eine normale .NET-Zeichenfolge umwandeln sollte, da dadurch alle Sicherheitsmaßnahmen aufgehoben werden. Ich weiß . Aber im Moment macht mein Programm sowieso alles mit gewöhnlichen Zeichenfolgen, und ich versuche, seine Sicherheit zu verbessern, und obwohl ich eine API verwenden werde, die mir einen SecureString zurückgibt, versuche ich nicht , diese zu verwenden, um meine Sicherheit zu erhöhen.

Ich kenne Marshal.SecureStringToBSTR, aber ich weiß nicht, wie ich diesen BSTR nehmen und daraus einen System.String machen soll.

Für diejenigen, die wissen möchten, warum ich dies jemals tun möchte, nehme ich ein Passwort von einem Benutzer und sende es als HTML-Formular POST, um den Benutzer auf einer Website anzumelden. Also ... das muss wirklich mit verwalteten, unverschlüsselten Puffern gemacht werden. Wenn ich überhaupt Zugriff auf den nicht verwalteten, unverschlüsselten Puffer erhalten könnte, könnte ich mir vorstellen, dass ich Byte-für-Byte-Streams in den Netzwerk-Stream schreiben könnte, und hoffe, dass das Passwort dadurch auf dem gesamten Weg sicher bleibt. Ich hoffe auf eine Antwort auf mindestens eines dieser Szenarien.

Andrew Arnott
quelle

Antworten:

191

Verwenden Sie die System.Runtime.InteropServices.MarshalKlasse:

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

Wenn Sie das Erstellen eines verwalteten Zeichenfolgenobjekts vermeiden möchten, können Sie auf die Rohdaten zugreifen, indem Sie 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
quelle
1
Ich habe auch Jahre später noch meine Stimme bekommen, danke für die Hilfe! Nur eine kurze Anmerkung: Dies funktioniert auch als statische, in seinem eigenen Speicher.
John Suit
Ich habe 4,6 Sekunden gebraucht StopWatchund SecureStringToStringgebraucht, um zu laufen. Es ist zu langsam für mich. Bekommt jemand die gleiche Zeit oder etwas schneller?
Radbyx
@radbyx In einem schnellen und schmutzigen Testaufbau kann ich es 1000 Mal in 76 ms aufrufen. Der erste Aufruf dauert 0,3 ms und nachfolgende Aufrufe ~ 0,07 ms. Wie groß ist Ihre sichere Zeichenfolge und welche Version des Frameworks verwenden Sie?
Rasmus Faber
Die Länge meines SecureString beträgt 168. Ich verwende .NET Framework 3.5, wenn dies Ihre Frage beantwortet hat. Ich habe 5-10 mal versucht, ist immer um 4.5-4.65 sec ~ Ich würde gerne Ihre Zeit bekommen
Radbyx
@RasmusFaber Mein schlechtes, ich hatte ein Database.GetConnectionString()in Ihren Code eingefügt, um meinen SecureString zu erhalten, was der böse Teil war, der fast 5 Sekunden dauerte (und ja, ich sollte das untersuchen! :) Ihr Code hat in meiner Stoppuhr 0,00 Mili Sekunden gedauert, also ist es alles gut. Danke, dass du mich in die richtige Richtung gelenkt hast.
Radbyx
108

Natürlich wissen Sie, wie dies den gesamten Zweck eines SecureString zunichte macht, aber ich werde es trotzdem wiederholen.

Wenn Sie einen Einzeiler möchten, versuchen Sie Folgendes: (Nur .NET 4 und höher)

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

Wobei SecurePassword ein SecureString ist.

Steve In CO
quelle
10
Obwohl es den Zweck in der Produktion zunichte macht, ist Ihre Lösung perfekt für Unit-Tests. Vielen Dank.
beterthanlife
Dies half mir herauszufinden, dass ein SecureString (System.Security.SecureString) nicht an meinen ApiController (Webapi) übergeben wurde. Thx
granadaCoder
5
Beachten Sie in PowerShell dies ist[System.Net.NetworkCredential]::new('', $securePassword).Password
stijn
1
@ TheIncorrigible1 kannst du das näher erläutern? ZB wann ist ''nicht der gleiche Typ wie [String]::Empty? Funktioniert auch New-Object Net.Credentialnicht für mich: Typ [Net.Credential] kann nicht gefunden werden: Überprüfen Sie, ob die Assembly mit diesem Typ geladen ist
9.
2
Der Zweck eines SecureString wird dadurch zunichte gemacht, dass eine unverschlüsselte Kopie Ihres SecureString-Inhalts in eine normale Zeichenfolge eingefügt wird. Jedes Mal, wenn Sie dies tun, fügen Sie dem Speicher mindestens eine (und bei Garbage Collection möglicherweise mehrere) Kopien Ihrer unverschlüsselten Zeichenfolge hinzu. Dies wird als Risiko für einige sicherheitsrelevante Anwendungen angesehen, und SecureString wurde speziell implementiert, um das Risiko zu verringern.
Steve In CO
49

Dang. Gleich nach dem Posten fand ich die Antwort tief in diesem Artikel . Wenn jedoch jemand weiß, wie er auf den nicht verwalteten, unverschlüsselten IntPtr-Puffer zugreifen kann, den diese Methode byteweise verfügbar macht, damit ich kein verwaltetes Zeichenfolgenobjekt daraus erstellen muss, um meine Sicherheit hoch zu halten, fügen Sie bitte eine Antwort hinzu. :) :)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
Andrew Arnott
quelle
Sie können sicherlich das unsafeSchlüsselwort und a verwenden char*, einfach anrufen bstr.ToPointer()und umsetzen.
Ben Voigt
@BenVoigt BSTR hat aus Sicherheitsgründen nach den Zeichenfolgendaten einen Nullterminator, erlaubt aber auch Nullzeichen, die in die Zeichenfolge eingebettet sind. Es ist also etwas komplizierter. Sie müssen auch das Längenpräfix abrufen, das sich vor diesem Zeiger befindet. docs.microsoft.com/en-us/previous-versions/windows/desktop/…
Wim Coenen
@WimCoenen: Richtig, aber unwichtig. Die im BSTR gespeicherte Länge ist eine Kopie der Länge, die bereits bei verfügbar ist SecureString.Length.
Ben Voigt
@ BenVoigt ah, mein schlechtes. Ich dachte, SecureString hat keine Informationen über die Zeichenfolge verfügbar gemacht.
Wim Coenen
@WimCoenen: SecureStringversucht nicht, den Wert auszublenden, sondern zu verhindern, dass Kopien des Werts in Regionen erstellt werden, die nicht zuverlässig überschrieben werden können, z. B. Speicherplatz, Auslagerungsdatei usw. Die Absicht ist, dass die SecureStringLebensdauer absolut endet, wenn sie endet Es bleibt keine Kopie des Geheimnisses im Speicher. Es hindert Sie nicht daran, eine Kopie zu erstellen und zu verlieren, aber es tut es nie.
Ben Voigt
15

Meiner Meinung nach sind Erweiterungsmethoden der bequemste Weg, dies zu lösen.

Ich habe Steve in die ausgezeichnete Antwort von CO aufgenommen und sie wie folgt in eine Erweiterungsklasse eingeordnet, zusammen mit einer zweiten Methode, die ich hinzugefügt habe, um auch die andere Richtung (Zeichenfolge -> sichere Zeichenfolge) zu unterstützen, sodass Sie eine sichere Zeichenfolge erstellen und in diese konvertieren können eine normale Zeichenfolge danach:

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

Damit können Sie Ihre Saiten jetzt einfach so hin und her konvertieren :

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

Beachten Sie jedoch, dass die Dekodierungsmethode nur zum Testen verwendet werden sollte.

Matt
quelle
14

Ich denke, es wäre am besten, wenn SecureStringabhängige Funktionen kapseln würden ihre abhängige Logik in einer anonymen Funktion um die entschlüsselte Zeichenfolge im Speicher (einmal fixiert) besser kontrollieren zu können.

Die Implementierung zum Entschlüsseln von SecureStrings in diesem Snippet wird:

  1. Pin die Zeichenfolge im Speicher (was Sie tun möchten, aber in den meisten Antworten hier zu fehlen scheint).
  2. Pass seine Referenz auf die Func / Aktionsdelegate.
  3. Scrub es aus dem Speicher und lassen Sie den GC im finallyBlock los.

Dies macht es offensichtlich viel einfacher, Anrufer zu "standardisieren" und zu warten, als sich auf weniger wünschenswerte Alternativen zu verlassen:

  • Rückgabe der entschlüsselten Zeichenfolge von einer string DecryptSecureString(...)Hilfsfunktion.
  • Duplizieren dieses Codes, wo immer er benötigt wird.

Beachten Sie, dass Sie hier zwei Möglichkeiten haben:

  1. static T DecryptSecureString<T>Hiermit können Sie Funcvom Aufrufer aus auf das Ergebnis des Delegaten zugreifen (wie in der DecryptSecureStringWithFuncTestmethode gezeigt ).
  2. static void DecryptSecureStringist einfach eine "void" -Version, die einen ActionDelegaten in Fällen beschäftigt, in denen Sie tatsächlich nichts zurückgeben möchten / müssen (wie in der DecryptSecureStringWithActionTestmethode gezeigt).

Eine Beispielverwendung für beide finden Sie in der enthaltenen StringsTestKlasse.

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

Dies verhindert natürlich nicht den Missbrauch dieser Funktion auf folgende Weise. Achten Sie also darauf, dies nicht zu tun:

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

Viel Spaß beim Codieren!

Matt Borja
quelle
Warum nicht Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);anstelle des fixedAbschnitts verwenden?
sclarke81
@ sclarke81, gute Idee, aber Sie müssen verwenden [char], nicht [byte].
mklement0
Der Gesamtansatz ist vielversprechend, aber ich denke nicht, dass Ihr Versuch, die verwaltete Zeichenfolge zu fixieren, die die unsichere (Klartext-) Kopie enthält, effektiv ist: Stattdessen fixieren Sie das ursprüngliche Zeichenfolgenobjekt, mit dem Sie initialisiert String.Emptyhaben. nicht die neu zugewiesene Instanz, die von erstellt und zurückgegeben wurde Marshal.PtrToStringUni().
mklement0
7

Ich habe die folgenden Erweiterungsmethoden basierend auf der Antwort von rdev5 erstellt . Das Fixieren der verwalteten Zeichenfolge ist wichtig, da dadurch verhindert wird, dass der Garbage Collector sie verschiebt und Kopien zurücklässt, die Sie nicht löschen können.

Ich denke, der Vorteil meiner Lösung ist, dass kein unsicherer Code benötigt wird.

/// <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
quelle
Ihr Code leckt zwar keine Kopie der Zeichenfolge, stellt jedoch eine Verzweiflung dar . Nahezu jede Operation am System.StringObjekt erstellt nicht fixierte und nicht gelöschte Kopien. Deshalb ist das nicht eingebaut SecureString.
Ben Voigt
Schön, wenn auch auf Null aus dem gesamten String Sie verwenden müssen new char[length](oder mehrfach lengthmit sizeof(char)).
mklement0
@BenVoigt: Solange der actionDelegat keine Kopien der temporären, angehefteten und dann auf Null gesetzten Zeichenfolge erstellt, sollte dieser Ansatz so sicher oder unsicher sein wie er SecureStringselbst - um Letzteres zu verwenden, muss auch eine Klartextdarstellung verwendet werden irgendwann erstellt werden, da sichere Zeichenfolgen keine Konstrukte auf Betriebssystemebene sind; Die relative Sicherheit besteht darin, die Lebensdauer dieser Zeichenfolge zu kontrollieren und sicherzustellen, dass sie nach der Verwendung gelöscht wird.
mklement0
@ mklement0: SecureStringhat keine Mitgliedsfunktionen und überladene Operatoren, die überall Kopien erstellen . System.Stringtut.
Ben Voigt
1
@ mklement0: Was verdammt absurd ist, wenn man bedenkt, dass es an den NetworkCredentialKonstruktor weitergegeben wird, der a akzeptiert SecureString.
Ben Voigt
0

Dieser C # -Code ist genau das, was Sie wollen.

%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
quelle
0

Ich habe aus dieser Antwort von sclarke81 abgeleitet . Ich mag seine Antwort und ich benutze das Derivat, aber sclarke81 hat einen Fehler. Ich habe keinen Ruf, kann also keinen Kommentar abgeben. Das Problem scheint klein genug zu sein, dass es keine weitere Antwort rechtfertigte und ich es bearbeiten konnte. So tat ich. Es wurde abgelehnt. Jetzt haben wir also eine andere Antwort.

sclarke81 Ich hoffe du siehst das (endlich):

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

sollte sein:

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

Und die vollständige Antwort mit der Fehlerbehebung:


    /// 
    /// 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
quelle
Guter Punkt; Ich habe einen Kommentar zu der referenzierten Antwort hinterlassen, der das OP benachrichtigen sollte.
mklement0
0

Die endgültige Arbeitslösung gemäß der Lösung von sclarke81 und den Korrekturen von John Flaherty lautet:

    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
quelle
-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
quelle
Diese Antwort hat einen Speicherverlust.
Ben Voigt
@BenVoigt Kannst du bitte weiter erklären, wie dies zu einem Speicherverlust führt?
El Ronnoco
4
@ElRonnoco: Nichts gibt das BSTRexplizit frei und es ist kein .NET-Objekt, also kümmert sich der Garbage Collector auch nicht darum. Vergleiche mit stackoverflow.com/a/818709/103167, das 5 Jahre zuvor veröffentlicht wurde und nicht leckt .
Ben Voigt
Diese Antwort funktioniert nicht auf Nicht-Windows-Plattformen. PtrToStringAuto ist falsch für eine Erklärung siehe: github.com/PowerShell/PowerShell/issues/…
K. Frank
-5

Wenn Sie a StringBuilderanstelle von a verwenden string, können Sie den tatsächlichen Wert im Speicher überschreiben, wenn Sie fertig sind. Auf diese Weise bleibt das Kennwort erst im Speicher hängen, wenn die Speicherbereinigung es aufnimmt.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());
Michael Liben
quelle
2
Während dies zutrifft, kann der Garbage Collector den StringBuilder-Puffer während der Generationskomprimierung dennoch im Speicher verschieben, wodurch das "Überschreiben des tatsächlichen Werts" fehlschlägt, da eine andere (oder mehrere) übrig gebliebene Kopie nicht zerstört wird.
Ben Voigt
4
Dies beantwortet die Frage nicht einmal aus der Ferne.
Jay Sullivan