如何将SecureString转换为System.String?

时间:2009-05-04 03:36:25

标签: c# .net security encryption

所有关于通过创建System.String来解除对SecureString的保护的所有保留一边,如何才能完成?

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

我相信很多熟悉SecureString的人会回应说,永远不应该将SecureString转换为普通的.NET字符串,因为它会删除所有安全保护。 我知道。但是现在我的程序用普通字符串完成所有操作,我正在尝试增强其安全性,虽然我将使用返回SecureString的API给我,但我尝试用它来增加我的安全性。

我知道Marshal.SecureStringToBSTR,但我不知道如何使用BSTR并从中生成System.String。

对于那些可能要求知道我为什么要这样做的人,好吧,我正在从用户那里获取密码并将其作为html表单POST提交以将用户登录到网站。所以......这真的必须使用托管的,未加密的缓冲区。如果我甚至可以访问非托管的,未加密的缓冲区,我想我可以在网络流上进行逐字节流写入,并希望这样可以保证密码的安全性。我希望能够回答至少其中一种情况。

12 个答案:

答案 0 :(得分:174)

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

如果您想避免创建托管字符串对象,可以使用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);
  }
}

答案 1 :(得分:89)

显然你知道这是如何破坏SecureString的整个目的的,但无论如何我都会重申它。

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

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

其中securePassword是SecureString。

答案 2 :(得分:44)

荡。 正确发布后我在this article深处找到答案。但是,如果有人知道如何访问此方法公开的IntPtr非托管,未加密的缓冲区,一次一个字节,以便我不必创建托管字符串对象以保持我的安全性,请添加答案。 :)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}

答案 3 :(得分:13)

我认为SecureString依赖函数最好在匿名函数中封装它们的依赖逻辑,以便更好地控制内存中的解密字符串(一旦固定)。

在此代码段中解密SecureStrings的实现将:

  1. 将字符串固定在内存中(这是您想要做的,但这里的大多数答案似乎都没有。)
  2. its reference传递给Func / Action代表。
  3. 从内存中擦除它并在finally块中释放GC。
  4. 这显然使“标准化”和维护呼叫者更容易,而不是依赖于不太理想的替代方案:

    • string DecryptSecureString(...)辅助函数返回解密后的字符串。
    • 在任何需要的地方复制此代码。

    请注意,您有两种选择:

    1. static T DecryptSecureString<T>,允许您从调用者访问Func委托的结果(如DecryptSecureStringWithFunc测试方法中所示)。
    2. static void DecryptSecureString只是一个“无效”版本,在您实际上不希望/需要返回任何内容的情况下使用Action委托(如DecryptSecureStringWithAction测试方法中所示)。
    3. 两者的示例用法可以在包含的StringsTest类中找到。

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

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

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

      快乐的编码!

答案 4 :(得分:9)

在我看来,扩展方法是解决此问题的最舒适方式。

我带了example here Steve in CO's并将其放入扩展类中,如下所示,以及我添加的第二种方法,以支持其他方向(字符串 - &gt;安全字符串),所以你可以创建一个安全的字符串,然后将其转换为普通字符串:

$mail->isSendmail();

有了这个,您现在可以简单地来回转换字符串,如下所示:

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

但请记住,解码方法只应用于测试。

答案 5 :(得分:4)

我基于answer from rdev5创建了以下扩展方法。固定托管字符串非常重要,因为它可以防止垃圾收集器在其周围移动并留下无法擦除的副本。

我认为我的解决方案的优点是不需要不安全的代码。

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

答案 6 :(得分:0)

您想要的是此C#代码。

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

答案 7 :(得分:0)

使用上面的示例,我的案例变得更加明显,而不是使用函数委托回调,当然,这取决于开发人员。

 public class SecureStringContext : IDisposable
{
    #region fields
    private GCHandle? _gcHandler = null;
    private string _insecureString = null;
    private IntPtr? _insecureStringPointer = null;
    private SecureString _secureString = null;      
    #endregion

    #region ctor
    public SecureStringContext(SecureString secureString)
    {
        _secureString = secureString;
        _secureString.MakeReadOnly();
        DecryptSecureString();

    }
    #endregion

    #region methos
    /// <summary>
    /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
    /// </summary>
    private string DecryptSecureString()
    {
        _insecureStringPointer = IntPtr.Zero;
        _insecureString = String.Empty;
        _gcHandler = GCHandle.Alloc(_insecureString, GCHandleType.Pinned);

        _insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(_secureString);
        _insecureString = Marshal.PtrToStringUni(_insecureStringPointer.GetValueOrDefault(IntPtr.Zero));

        return _insecureString;
    }

    private void WipeInsecureString()
    {
        //clear memory immediately - don't wait for garbage collector
        unsafe
        {
            fixed (char* ptr = _insecureString)
            {
                for (int i = 0; i < _insecureString.Length; i++)
                {
                    ptr[i] = '\0';
                }
            }
        }
        _insecureString = null;
    }
    #endregion

    #region properties
    public string InsecureString { get => _insecureString; }
    #endregion

    #region dispose
    public void Dispose()
    {
        //clear memory immediately - don't wait for garbage collector
        WipeInsecureString();
    }
    #endregion
}

用法(请记住,一旦引用也会被保存。)

using (var secureStringContext = new SecureStringContext(FabricSettingsHelper.GetConnectionSecureString()))
{
   //this is the clear text connection string
   x.UseSqlServerStorage(secureStringContext.InsecureString);
} //disposed clear text is removed from memory

答案 8 :(得分:0)

我来自This answer by sclarke81。我喜欢他的回答,但我使用的是派生词,但sclarke81存在错误。我没有声誉,所以我无法发表评论。该问题似乎很小,无法保证没有其他答案,我可以对其进行编辑。所以我做了。被拒绝了。所以现在我们有了另一个答案。

sclarke81我希望您终于看到这个:

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

应为:

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

以及有关错误修复的完整答案:


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

答案 9 :(得分:0)

根据sclarke81解决方案和John Flaherty修复的最终工作解决方案是:

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

答案 10 :(得分:-3)

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

答案 11 :(得分:-3)

如果使用.meteor而不是StringBuilder,则可以在完成后覆盖内存中的实际值。这样,密码就不会在内存中流传,直到垃圾回收它为止。

string