如何将SecureString转换为System.String?

所有关于通过创build一个System.String来取消安全SecureString的保留 ,如何完成?

如何将一个普通的System.Security.SecureString转换为System.String?

我相信很多熟悉SecureString的人都会回应说,绝对不应该将SecureString转换为普通的.NETstring,因为它会去除所有的安全保护。 我知道 但是现在我的程序用普通的string来处理所有事情,而且我正在尝试增强它的安全性,尽pipe我将要使用一个返回SecureString的API,我并不想用它来提高我的安全性。

我知道Marshal.SecureStringToBSTR,但我不知道如何把BSTR,并使一个System.String出它。

对于那些可能要求知道为什么我会想要这样做的人,那么,我正在从用户那里获取一个密码,并将其提交为HTML表单POST,以将用户login到网站。 所以…这确实需要使用托pipe的,未encryption的缓冲区来完成。 如果我甚至可以访问非托pipe的,未encryption的缓冲区,我想我可以在networkingstream上逐字节地写数据stream,并希望能够保证整个密码的安全。 我希望至less有一个这样的情况的答案。

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

如果您想要避免创build托pipestring对象,则可以使用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); } } 

显然你知道这是如何破坏SecureString的全部目的,但我会重申它。

如果你想要一行代码,试试这个:(仅限于.NET 4及以上版本)

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

securePassword是一个SecureString。

荡。 在发布这篇文章之后,我发现了这篇文章的深层答案。 但是,如果有人知道如何访问这个方法公开的IntPtr非托pipe,未encryption的缓冲区,每次一个字节,这样我就不必创build一个托pipestring对象来保持我的安全性,请添加一个答案。 🙂

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

我认为最好是依赖于SecureString函数它们的依赖逻辑封装在一个匿名函数中,以便更好地控制内存中的解密string(一旦被locking)。

在这个片段中解密SecureStrings的实现将会:

  1. 在内存中固定string(这是你想要做的,但似乎从大多数答案这里都没有)。
  2. 将其引用传递给Func / Action委托。
  3. 从内存中擦洗并释放finally块中的GC。

这显然使“标准化”和维护呼叫者比依靠较不理想的替代scheme容易得多:

  • string DecryptSecureString(...)辅助函数返回解密的string。
  • 在需要的地方复制这个代码。

注意这里,你有两个select:

  1. static T DecryptSecureString<T>它允许您从调用方访问Func委托的结果(如DecryptSecureStringWithFunctesting方法所示)。
  2. static void DecryptSecureString只是一个“无效”版本,在实际上不需要/不需要返回任何东西(如DecryptSecureStringWithActiontesting方法中所示)的情况下使用Action委托。

包含的StringsTest类可以find两者的示例用法。

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

显然,这并不妨碍以下方式滥用此function,所以请注意不要这样做:

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

快乐的编码!

在我看来, 扩展方法是解决这个问题最舒适的方法。 我把史考特带到了CO的 优秀答案中,并把它放在一个扩展类中,如下所示,以及另外一个方法来支持另一个方向:

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

有了这个,你现在可以像这样来回转换你的string

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

但请记住解码方法只能用于testing。

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

如果使用StringBuilder而不是string ,那么当完成时可以覆盖内存中的实际值。 这样的方式密码将不会在内存中挂起,直到垃圾收集拿起它。

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