如何将 SecureString 转换为 System.String?

Posted

技术标签:

【中文标题】如何将 SecureString 转换为 System.String?【英文标题】:How to convert SecureString to System.String? 【发布时间】:2010-10-23 13:16:38 【问题描述】:

所有关于通过创建 System.String 来解除 SecureString 安全的保留除了,怎么做?

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

我相信许多熟悉 SecureString 的人都会回答说,永远不应该将 SecureString 转换为普通的 .NET 字符串,因为它会删除所有安全保护。 我知道。但是现在我的程序无论如何都用普通字符串做所有事情,我正在尝试增强它的安全性,虽然我将使用一个向我返回 SecureString 的 API,但我试图用它来增加我的安全性。

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

对于那些可能想知道我为什么要这样做的人,好吧,我从用户那里获取密码并将其作为 html 表单 POST 提交以将用户登录到网站。所以......这确实必须使用托管的未加密缓冲区来完成。如果我什至可以访问未管理的、未加密的缓冲区,我想我可以在网络流上逐字节地进行流写入,并希望整个过程都能保证密码的安全。我希望至少能找到其中一种情况的答案。

【问题讨论】:

【参考方案1】:

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

【讨论】:

多年后也得到了我的支持,感谢您的帮助!只是一个简短的说明:这也可以作为一个静态的,在它自己的记忆中。 我用StopWatchSecureStringToString 运行了4.6秒。对我来说太慢了。有人得到相同的时间或更快的时间吗? @radbyx 在快速而肮脏的测试设置中,我可以在 76 毫秒内调用它 1000 次。第一次调用需要 0.3 毫秒,后续调用大约需要 0.07 毫秒。您的安全字符串有多大?您使用的是哪个版本的框架? 我的secureString的长度是168。如果这回答了你的问题,我正在使用.NET Framework 3.5?我尝试了 5-10 次总是在 4.5-4.65 秒左右~我很想得到你的时间 代码应使用SecureStringToBSTR,因为SecureString 可以包含\0 作为非终止字符,但SecureStringToGlobalAllocUnicode 将其视为以空值结尾的字符串。【参考方案2】:

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

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

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

其中securePassword 是一个SecureString。

【讨论】:

虽然它确实违背了生产目的,但您的解决方案非常适合单元测试。谢谢。 这帮助我弄清楚 SecureString (System.Security.SecureString) 没有被传递给我的 ApiController (webapi)。谢谢 注意在 PowerShell 中这是[System.Net.NetworkCredential]::new('', $securePassword).Password @TheIncorrigible1 你能详细说明一下吗?例如。 '' 何时与 [String]::Empty 不同类型? New-Object Net.Credential 也不适合我:找不到类型 [Net.Credential]:验证是否加载了包含此类型的程序集 它违背了 SecureString 的目的,因为它将您的 SecureString 内容的非加密副本转换为普通字符串。每次您这样做时,您都会将未加密字符串的至少一个(并且可能是多个垃圾收集)副本添加到内存中。对于某些安全敏感的应用程序来说,这被认为是一种风险,因此专门实施了 SecureString 以降低风险。【参考方案3】:

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

static String SecureStringToString(SecureString value)

    IntPtr bstr = Marshal.SecureStringToBSTR(value);

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

【讨论】:

您当然可以使用unsafe 关键字和char*,只需调用bstr.ToPointer() 并进行转换。 @BenVoigt BSTR 为了安全起见,在字符串数据之后有一个空终止符,但也允许在字符串中嵌入空字符。所以它比这更复杂一些,您还需要检索位于该指针之前的长度前缀。 docs.microsoft.com/en-us/previous-versions/windows/desktop/… @WimCoenen:没错但不重要。存储在 BSTR 中的长度将是 SecureString.Length 中已有长度的副本。 @BenVoigt 啊,我的错。我认为 SecureString 没有公开有关字符串的任何信息。 @WimCoenen: SecureString 不是试图隐藏该值,而是试图防止将该值的副本复制到无法可靠覆盖的区域,例如垃圾收集的内存、页面文件等. 目的是当SecureString 生命周期结束时,绝对不会在内存中保留任何秘密副本。它不会阻止您制作和泄露副本,但永远不会。【参考方案4】:

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

我把Steve in CO'sexcellent answer放到一个扩展类中,如下所示,再加上我添加的第二种方法来支持另一个方向(字符串->安全字符串),所以你可以创建一个安全字符串然后将其转换为普通字符串:

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;
    

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

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

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

【讨论】:

我相信 ToSecureString 也应该只用于测试,因为 SecureString 的目的是防止字符串超出内存。 @ryanwebjackson - 是的。此外,这还不如真正的加密。没有使用密钥,因此每个人都可以显示明文。所以它不应该被命名为“安全”。 "security through obscurity" 的另一个例子。【参考方案5】:

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

在这个 sn-p 中解密 SecureStrings 的实现将:

    将字符串固定在内存中(这是您想要做的,但这里的大多数答案似乎都缺少)。 将 its reference 传递给 Func/Action 委托。 从内存中清除它并释放 finally 块中的 GC。

与依赖不太理想的替代方案相比,这显然使“标准化”和维护调用者变得容易得多:

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

请注意,您有两种选择:

    static T DecryptSecureString&lt;T&gt; 允许您从调用者访问Func 委托的结果(如DecryptSecureStringWithFunc 测试方法中所示)。 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;
            );
        
    

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

编码愉快!

【讨论】:

为什么不使用Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length); 而不是fixed 部分? @sclarke81,好主意,但您需要使用[char],而不是[byte] 整体方法很有希望,但我不认为您尝试固定包含不安全(纯文本)副本的托管字符串是有效的:您要固定的是 original 已初始化为String.Empty 的字符串对象,而不是Marshal.PtrToStringUni() 创建并返回的新分配实例。【参考方案6】:

我基于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;
    );

【讨论】:

虽然您的代码不会泄露字符串的副本,但它仍然代表pit of despair。几乎对System.String 对象的每个操作都会生成未固定和未擦除的副本。这就是为什么 SecureString 没有内置它。 很好,不过要将整个字符串归零,您必须使用new char[length](或将lengthsizeof(char) 相乘)。 @BenVoigt:只要action 委托不创建临时、固定、然后归零的字符串的副本,这种方法应该与SecureString 本身一样安全或不安全- 要使用后者,也必须在某些时候创建纯文本表示,因为安全字符串不是操作系统级别的构造;相对安全性来自控制该字符串的生命周期并确保它在使用后被擦除。 @mklement0: SecureString 没有成员函数和重载运算符,可以在所有地方进行复制。 System.String 确实如此。 @mklement0:考虑到它将它传递给确实接受SecureStringNetworkCredential 构造函数,这非常荒谬。【参考方案7】:

根据 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;
            );
        
    

【讨论】:

【参考方案8】:

我来自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;
        );
    

【讨论】:

好点;我对引用的答案发表了评论,应该通知 OP。【参考方案9】:

这个 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;
        
    

【讨论】:

我不确定你是否打算这样做,但参数需要以“this”为前缀才能成为正确的扩展方法【参考方案10】:

作为答案接受的代码是正确的,并且在大多数情况下都可以使用,但是正如 cmets 中提到的那样,使用 BSTR 会更好,并且可以涵盖所有情况:

private string SecureStringToString(SecureString value) 
    IntPtr valuePtr = IntPtr.Zero;
    try 
        valuePtr = Marshal.SecureStringToBSTR(value);
        return Marshal.PtrToStringBSTR(valuePtr);
     finally 
        Marshal.ZeroFreeBSTR(valuePtr);
    

【讨论】:

【参考方案11】:

使用以下内容:

var plaintextPwd = new System.Net.NetworkCredential("", <securestring with your encrypted password>).Password

【讨论】:

【参考方案12】:
// 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;

【讨论】:

这个答案有内存泄漏。 @BenVoigt 您能否进一步解释一下这是如何导致内存泄漏的? @ElRonnoco:没有什么能明确释放BSTR,它不是.NET 对象,所以垃圾收集器也不会处理它。与 5 年前发布的 ***.com/a/818709/103167 相比,没有泄露。 此答案不适用于非 Windows 平台。 PtrToStringAuto 是错误的解释见:github.com/PowerShell/PowerShell/issues/…【参考方案13】:

如果您使用StringBuilder 而不是string,您可以在完成后覆盖内存中的实际值。这样密码就不会在内存中徘徊,直到垃圾收集器拾取它。

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

【讨论】:

虽然这是真的,垃圾收集器仍然可能在分代压缩期间在内存中移动 StringBuilder 缓冲区,这使得“覆盖实际值”失败,因为还有另一个(或更多)剩余副本没有被破坏。 这甚至不能远程回答问题。

以上是关于如何将 SecureString 转换为 System.String?的主要内容,如果未能解决你的问题,请参考以下文章

UiPath中如何安全的保存账号密码

如何将 SecureString 从一个进程传递到另一个进程?

Powershell将string转化为SecureString

MySQL SecureString 作为连接字符串

为啥 TFS 机密不是 SecureString?

Security.SecureString 参数不接受字符串