How to convert SecureString to System.String?

匿名 (未验证) 提交于 2019-12-03 02:08:02

问题:

All reservations about unsecuring your SecureString by creating a System.String out of it aside, how can it be done?

How can I convert an ordinary System.Security.SecureString to System.String?

I'm sure many of you who are familiar with SecureString are going to respond that one should never transform a SecureString to an ordinary .NET string because it removes all security protections. I know. But right now my program does everything with ordinary strings anyway, and I'm trying to enhance its security and although I'm going to be using an API that returns a SecureString to me I am not trying to use that to increase my security.

I'm aware of Marshal.SecureStringToBSTR, but I don't know how to take that BSTR and make a System.String out of it.

For those who may demand to know why I would ever want to do this, well, I'm taking a password from a user and submitting it as an html form POST to log the user into a web site. So... this really has to be done with managed, unencrypted buffers. If I could even get access to the unmanaged, unencrypted buffer I imagine I could do byte-by-byte stream writing on the network stream and hope that that keeps the password secure the whole way. I'm hoping for an answer to at least one of these scenarios.

回答1:

Use the System.Runtime.InteropServices.Marshal class:

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

If you want to avoid creating a managed string object, you can access the raw data using 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);   } } 


回答2:

Obviously you know how this defeats the whole purpose of a SecureString, but I'll restate it anyway.

If you want a one-liner, try this: (.NET 4 and above only)

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

Where securePassword is a SecureString.



回答3:

Dang. right after posting this I found the answer deep in this article. But if anyone knows how to access the IntPtr unmanaged, unencrypted buffer that this method exposes, one byte at a time so that I don't have to create a managed string object out of it to keep my security high, please add an answer. :)

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


回答4:

I think it would be best for SecureString dependent functions to encapsulate their dependent logic in an anonymous function for better control over the decrypted string in memory (once pinned).

The implementation for decrypting SecureStrings in this snippet will:

  1. Pin the string in memory (which is what you want to do but appears to be missing from most answers here).
  2. Pass its reference to the Func/Action delegate.
  3. Scrub it from memory and release the GC in the finally block.

This obviously makes it a lot easier to "standardize" and maintain callers vs. relying on less desirable alternatives:

  • Returning the decrypted string from a string DecryptSecureString(...) helper function.
  • Duplicating this code wherever it is needed.

Notice here, you have two options:

  1. static T DecryptSecureString<T> which allows you to access the result of the Func delegate from the caller (as shown in the DecryptSecureStringWithFunc test method).
  2. static void DecryptSecureString is simply a "void" version which employ an Action delegate in cases where you actually don't want/need to return anything (as demonstrated in the DecryptSecureStringWithAction test method).

Example usage for both can be found in the StringsTest class included.

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

Obviously, this doesn't prevent abuse of this function in the following manner, so just be careful not to do this:

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

Happy coding!



回答5:

In my opinion, extension methods are the most comfortable way to solve this.

I took Steve in CO's excellent answer and put it into an extension class as follows, together with a second method I added to support the other direction (string -> secure string) as well, so you can create a secure string and convert it into a normal string afterwards:

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

With this, you can now simply convert your strings back and forth like so:

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

But keep in mind the decoding method should only be used for testing.



回答6:

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


回答7:

If you use a StringBuilder instead of a string, you can overwrite the actual value in memory when you are done. That way the password won't hang around in memory until garbage collection picks it up.

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!