如何将SecureString转换为System.String?

And*_*ott 147 .net c# security encryption

有关创建以System.String出它不保护你的SecureString的所有预订一边,怎么能做到呢?

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

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

我知道Marshal.SecureStringToBSTR,但我不知道如何取出BSTR并从中制作System.String.

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

Ras*_*ber 183

使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果要避免创建托管字符串对象,可以使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 代码应使用“SecureStringToBSTR”,因为“SecureString”可以包含“\0”作为非终止字符,但“SecureStringToGlobalAllocUnicode”将其视为空终止字符串。 (2认同)

Ste*_* CO 97

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

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

string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;
Run Code Online (Sandbox Code Playgroud)

其中securePassword是SecureString.

  • 虽然它确实破坏了生产的目的,但您的解决方案非常适合单元测试.谢谢. (10认同)
  • 在PowerShell中注意这是`[System.Net.NetworkCredential] :: new('',$ securePassword).Password` (4认同)
  • @TheIncorrigible1 你能详细说明一下吗?例如,什么时候 `''` 与 `[String]::Empty` 类型不同?另外 `New-Object Net.Credential` 对我不起作用:*找不到类型 [Net.Credential]:验证包含此类型的程序集是否已加载* (2认同)
  • 它违背了 SecureString 的目的,因为它将 SecureString 内容的非加密副本转换为普通字符串。每次执行此操作时,都会将未加密字符串的至少一个(并且垃圾收集可能会添加多个)副本添加到内存中。对于某些安全敏感的应用程序来说,这被认为是一种风险,而 SecureString 是专门为了降低风险而实施的。 (2认同)

And*_*ott 47

荡. 张贴这后,我找到了答案深藏在这篇文章.但是,如果有人知道如何访问此方法公开的IntPtr非托管,未加密的缓冲区,一次一个字节,以便我不必创建托管字符串对象以保持我的安全性,请添加答案.:)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*att 14

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

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

  1. 将字符串固定在内存中(这是你想要做的,但在这里似乎缺少大多数答案).
  2. 其引用传递给Func/Action委托.
  3. 从内存中擦除它并释放finally块中的GC .

这显然使得"标准化"和维护呼叫者更容易,而不是依赖于不太理想的替代方案:

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

请注意,您有两种选择:

  1. static T DecryptSecureString<T>它允许您Func从调用者访问委托的结果(如DecryptSecureStringWithFunc测试方法中所示).
  2. static void DecryptSecureString只是一个"无效"版本,Action在你实际上不需要/需要返回任何内容的情况下使用委托(如DecryptSecureStringWithAction测试方法中所示).

两者的示例用法可以在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;
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

[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
}
Run Code Online (Sandbox Code Playgroud)

快乐的编码!

  • 总体方法很有希望,但我认为您尝试固定包含不安全(纯文本)副本的托管字符串的尝试并不有效:您固定的是已初始化为`的 _original_ string 对象String.Empty`,不是由 `Marshal.PtrToStringUni()` 创建和返回的新分配的实例。 (2认同)

Mat*_*att 12

在我看来,扩展方法是解决这个问题最舒服的方法.

我把史蒂夫用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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
Run Code Online (Sandbox Code Playgroud)

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


scl*_*e81 5

我根据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;
    });
}
Run Code Online (Sandbox Code Playgroud)