所有关于通过创建System.String来解除对SecureString的保护的所有保留一边,如何才能完成?
如何将普通的System.Security.SecureString转换为System.String?
我相信很多熟悉SecureString的人会回应说,永远不应该将SecureString转换为普通的.NET字符串,因为它会删除所有安全保护。 我知道。但是现在我的程序用普通字符串完成所有操作,我正在尝试增强其安全性,虽然我将使用返回SecureString的API给我,但我不尝试用它来增加我的安全性。
我知道Marshal.SecureStringToBSTR,但我不知道如何使用BSTR并从中生成System.String。
对于那些可能要求知道我为什么要这样做的人,好吧,我正在从用户那里获取密码并将其作为html表单POST提交以将用户登录到网站。所以......这真的必须使用托管的,未加密的缓冲区。如果我甚至可以访问非托管的,未加密的缓冲区,我想我可以在网络流上进行逐字节流写入,并希望这样可以保证密码的安全性。我希望能够回答至少其中一种情况。
答案 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的实现将:
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
}
快乐的编码!
答案 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