Cómo convertir SecureString a System.String?

Todas las reservas sobre la anulación de SecureString mediante la creación de un System.String de lado , ¿cómo se puede hacer?

¿Cómo puedo convertir un System.Security.SecureString ordinario a System.String?

Estoy seguro de que muchos de ustedes que están familiarizados con SecureString van a responder que uno nunca debe transformar un SecureString en una cadena ordinaria de .NET porque elimina todas las protecciones de seguridad. Lo sé Pero en este momento mi progtwig hace todo con cadenas normales de todos modos, y estoy tratando de mejorar su seguridad y aunque voy a utilizar una API que me devuelve SecureString, no estoy tratando de usar eso para boost mi seguridad.

Soy consciente de Marshal.SecureStringToBSTR, pero no sé cómo tomar ese BSTR y hacer un System.String fuera de él.

Para aquellos que pueden exigir saber por qué querría hacer esto, bueno, estoy tomando una contraseña de un usuario y presentándola como un formulario HTML POST para registrar al usuario en un sitio web. Entonces … esto realmente tiene que hacerse con almacenamientos intermedios administrados, sin encriptar. Si pudiera tener acceso al búfer no encriptado y sin encriptar, me imagino que podría escribir byte por byte en la secuencia de la red y espero que eso mantenga la contraseña segura todo el camino. Estoy esperando una respuesta para al menos uno de estos escenarios.

Use la clase 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); } } 

Si desea evitar la creación de un objeto de cadena administrado, puede acceder a los datos brutos 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); } } 

Obviamente, usted sabe cómo esto derrota el propósito de un SecureString, pero voy a replantearlo de todos modos.

Si desea un trazador de líneas único, intente esto: (.NET 4 y superior solamente)

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

Donde securePassword es un SecureString.

Dang. justo después de publicar esto encontré la respuesta en este artículo . Pero si alguien sabe cómo acceder al buffer no encriptado, no encriptado de IntPtr que expone este método, un byte a la vez para que no tenga que crear un objeto de cadena administrado para mantener alta mi seguridad, por favor agregue una respuesta. 🙂

 static String SecureStringToString(SecureString value) { IntPtr bstr = Marshal.SecureStringToBSTR(value); try { return Marshal.PtrToStringBSTR(bstr); } finally { Marshal.FreeBSTR(bstr); } } 

Creo que sería mejor para las funciones dependientes de SecureString encapsular su lógica dependiente en una función anónima para un mejor control sobre la cadena desencriptada en la memoria (una vez fijada).

La implementación para descifrar SecureStrings en este fragmento:

  1. Pin la cadena en la memoria (que es lo que quiere hacer, pero parece que falta en la mayoría de las respuestas aquí).
  2. Pase su referencia al delegado Func / Action.
  3. Frote desde la memoria y suelte el GC en el bloque finally .

Obviamente, esto hace que sea mucho más fácil “estandarizar” y mantener a las personas que llaman versus confiar en alternativas menos deseables:

  • Devolver la cadena desencriptada de una string DecryptSecureString(...) ayuda de string DecryptSecureString(...) .
  • Duplicando este código donde sea necesario.

Observe aquí, usted tiene dos opciones:

  1. static T DecryptSecureString que le permite acceder al resultado del delegado Func de la persona que llama (como se muestra en el método de prueba DecryptSecureStringWithFunc ).
  2. static void DecryptSecureString es simplemente una versión “nula” que emplea un delegado de Action en los casos en que realmente no desea / no necesita devolver nada (como se demostró en el método de prueba DecryptSecureStringWithAction ).

El uso de ejemplos para ambos se puede encontrar en la clase StringsTest incluida.

Strings.cs

 using System; using System.Runtime.InteropServices; using System.Security; namespace SecurityUtils { public partial class Strings { ///  /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return. ///  /// Generic type returned by Func delegate /// Func delegate which will receive the decrypted password pinned in memory as a String object /// Result of Func delegate public static T DecryptSecureString(SecureString secureString, Func 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); } } ///  /// Runs DecryptSecureString with support for Action to leverage void return type ///  ///  ///  public static void DecryptSecureString(SecureString secureString, Action action) { DecryptSecureString(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(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); } } } 

Obviamente, esto no evita el abuso de esta función de la siguiente manera, así que tenga cuidado de no hacer esto:

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

Feliz encoding!

En mi opinión, los métodos de extensión son la forma más cómoda de resolver esto.

Tomé a Steve en la excelente respuesta de CO y lo puse en una clase de extensión de la siguiente manera, junto con un segundo método que agregué para apoyar la otra dirección (cadena -> cadena segura) también, para que pueda crear una cadena segura y convertirla en una cadena normal después:

 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 esto, ahora puedes simplemente convertir tus cadenas de un lado a otro de la siguiente manera:

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

Pero tenga en cuenta que el método de deencoding solo debe usarse para las pruebas.

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

Si usa un StringBuilder lugar de una string , puede sobrescribir el valor real en la memoria cuando haya terminado. De esta forma, la contraseña no se mantendrá en la memoria hasta que la recolección de basura la recoja.

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