如何将 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);
【讨论】:
多年后也得到了我的支持,感谢您的帮助!只是一个简短的说明:这也可以作为一个静态的,在它自己的记忆中。 我用StopWatch
和SecureStringToString
运行了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<T>
允许您从调用者访问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]
(或将length
与sizeof(char)
相乘)。
@BenVoigt:只要action
委托不创建临时、固定、然后归零的字符串的副本,这种方法应该与SecureString
本身一样安全或不安全- 要使用后者,也必须在某些时候创建纯文本表示,因为安全字符串不是操作系统级别的构造;相对安全性来自控制该字符串的生命周期并确保它在使用后被擦除。
@mklement0: SecureString
没有成员函数和重载运算符,可以在所有地方进行复制。 System.String
确实如此。
@mklement0:考虑到它将它传递给确实接受SecureString
的NetworkCredential
构造函数,这非常荒谬。【参考方案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?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 SecureString 从一个进程传递到另一个进程?