将字符串转换为 SecureString

如何将 String转换为 SecureString

235078 次浏览

- 我就直说了-为什么?

您不能仅仅将所有字符串更改为安全字符串,然后突然您的应用程序就是“安全的”。安全字符串的设计目的是尽可能长时间地保持字符串加密,并且只在非常短的时间内解密,在对其执行操作后清除内存。

在担心应用程序字符串的安全性之前,我冒昧地说您可能有一些设计级别的问题需要处理。给我们一些更多的信息,你试图做什么,我们可能能够提供更好的帮助。

你不知道。使用 SecureString 对象的全部原因是为了避免创建字符串对象(该对象被加载到内存中并以明文形式保存,直到垃圾收集)。但是,可以通过追加字符来向 SecureString 添加字符。

var s = new SecureString();
s.AppendChar('d');
s.AppendChar('u');
s.AppendChar('m');
s.AppendChar('b');
s.AppendChar('p');
s.AppendChar('a');
s.AppendChar('s');
s.AppendChar('s');
s.AppendChar('w');
s.AppendChar('d');

我同意 Spence (+ 1) ,但是如果你是为了学习或者测试目的,你可以在字符串中使用 foreach,使用 AppendChar 方法将每个字符附加到 securestring。

方法有助于将字符串转换为安全字符串

private SecureString ConvertToSecureString(string password)
{
if (password == null)
throw new ArgumentNullException("password");


var securePassword = new SecureString();


foreach (char c in password)
securePassword.AppendChar(c);


securePassword.MakeReadOnly();
return securePassword;
}

你可以这样做:

string password = "test";
SecureString sec_pass = new SecureString();
Array.ForEach(password.ToArray(), sec_pass.AppendChar);
sec_pass.MakeReadOnly();
unsafe
{
fixed(char* psz = password)
return new SecureString(psz, password.Length);
}

这是一个廉价的林克戏法。

            SecureString sec = new SecureString();
string pwd = "abc123"; /* Not Secure! */
pwd.ToCharArray().ToList().ForEach(sec.AppendChar);
/* and now : seal the deal */
sec.MakeReadOnly();

如果您想将 stringSecureString的转换压缩为 LINQ语句,您可以将其表示如下:

var plain  = "The quick brown fox jumps over the lazy dog";
var secure = plain
.ToCharArray()
.Aggregate( new SecureString()
, (s, c) => { s.AppendChar(c); return s; }
, (s)    => { s.MakeReadOnly(); return s; }
);

但是,请记住,使用 LINQ并不能提高此解决方案的安全性。它和任何从 stringSecureString的转换都有同样的缺陷。只要原始 string保留在内存中,数据就是脆弱的。

也就是说,上面的语句能够提供的是保持 SecureString的创建、它的数据初始化以及最终锁定它不被修改。

没有花哨的 linq,没有手工添加所有字符,只是简单明了:

var str = "foo";
var sc = new SecureString();
foreach(char c in str) sc.appendChar(c);

还有另一种方法可以在 SecureStringString之间进行转换。

字符串到 SecureString

SecureString theSecureString = new NetworkCredential("", "myPass").SecurePassword;

2. SecureString 到 String

string theString = new NetworkCredential("", theSecureString).Password;

这是 链接

以下两个扩展应该可以解决这个问题:

  1. 对于 char数组

    public static SecureString ToSecureString(this char[] _self)
    {
    SecureString knox = new SecureString();
    foreach (char c in _self)
    {
    knox.AppendChar(c);
    }
    return knox;
    }
    
  2. And for string

    public static SecureString ToSecureString(this string _self)
    {
    SecureString knox = new SecureString();
    char[] chars = _self.ToCharArray();
    foreach (char c in chars)
    {
    knox.AppendChar(c);
    }
    return knox;
    }
    

Thanks to John Dagg for the AppendChar recommendation.

您可以使用这个简单的脚本

private SecureString SecureStringConverter(string pass)
{
SecureString ret = new SecureString();


foreach (char chr in pass.ToCharArray())
ret.AppendChar(chr);


return ret;
}

我只是想指出,所有的人说,“这不是 SecureString的重点”,许多人问这个问题可能是在一个应用程序中,无论是什么原因,合理与否,他们是 没有特别关心有一个临时副本的密码坐在堆上作为一个 GC-able 字符串,但他们必须使用一个 API,只有接受 SecureString对象。所以,你有一个应用程序,你不 关心无论密码是在堆上,也许它只是内部使用和密码只有在那里,因为它的基础网络协议需要,你发现那个字符串的密码存储不能被用来设置一个远程 PowerShell 运行空间-但没有简单,直接的一行程序来创建你需要的 SecureString。这是一个小小的不便——但是为了确保真正需要 SecureString的应用程序不会诱使作者使用 System.StringSystem.Char[]中介体,这样做可能是值得的。:-)

为了完整起见,我添加了两个单元测试和方法,将字符数组和字符串转换为 SecureString,然后再转换回来。您应该尽量避免使用字符串,只传入指向 char 数组或 char 数组本身的指针,就像我在这里提供的方法一样,因为字符串是不安全的,因为它们将数据以纯文本形式保存在托管内存中的时间相当不确定,直到下一次运行或强制执行 GC,最好尽快将您的字符数组放入 SecureString 中,并将其保留在那里,并以字符数组的形式再次读取它。

这些测试是这样的:

using NUnit.Framework;
using System;
using SecureStringExtensions;
using System.Security;


namespace SecureStringExtensions.Test
{
[TestFixture]
public class SecureStringExtensionsTest
{
[Test]
[TestCase(new char[] { 'G', 'O', 'A', 'T', '1', '2', '3' })]
public void CopyCharArrayToSecureStringAndCopyBackToCharArrayReturnsExpected(char[] inputChars)
{
SecureString sec = inputChars.ToSecureString();
var copiedFromSec = sec.FromSecureStringToCharArray();
CollectionAssert.AreEqual(copiedFromSec, inputChars);
}


[Test]
[TestCase("GOAT456")]
public void CopyStringToSecureStringAndCopyBackToUnsafeStringReturnsExpected(string inputString)
{
SecureString sec = inputString.ToSecureString();
var copiedFromSec = sec.FromSecureStringToUnsafeString();
Assert.AreEqual(copiedFromSec, inputString);
}
}
}

这里有我们的扩展方法:

using System;
using System.Runtime.InteropServices;
using System.Security;


namespace SecureStringExtensions
{
public static class SecureStringExtensions
{
public static SecureString ToSecureString(this string str)
{
return ToSecureString(str.ToCharArray());
}


public static SecureString ToSecureString(this char[] str)
{
var secureString = new SecureString();
Array.ForEach(str, secureString.AppendChar);
return secureString;
}


/// <summary>
/// Creates a managed character array from the secure string using methods in System.Runetime.InteropServices
/// copying data into a BSTR (unmanaged binary string) and then into a managed character array which is returned from this method.
/// Data in the unmanaged memory temporarily used are freed up before the method returns.
/// </summary>
/// <param name="secureString"></param>
/// <returns></returns>
public static char[] FromSecureStringToCharArray(this SecureString secureString)
{
char[] bytes;
var ptr = IntPtr.Zero;
try
{
//alloc unmanaged binary string  (BSTR) and copy contents of SecureString into this BSTR
ptr = Marshal.SecureStringToBSTR(secureString);
bytes = new char[secureString.Length];
//copy to managed memory char array from unmanaged memory
Marshal.Copy(ptr, bytes, 0, secureString.Length);
}
finally
{
if (ptr != IntPtr.Zero)
{
//free unmanaged memory
Marshal.ZeroFreeBSTR(ptr);
}
}
return bytes;
}


/// <summary>
/// Returns an unsafe string in managed memory from SecureString.
/// The use of this method is not recommended - use instead the <see cref="FromSecureStringToCharArray(SecureString)"/> method
/// as that method has not got multiple copies of data in managed memory like this method.
/// Data in unmanaged memory temporarily used are freed up before the method returns.
/// </summary>
/// <param name="secureString"></param>
/// <returns></returns>
public static string FromSecureStringToUnsafeString(this SecureString secureString)
{
if (secureString == null)
{
throw new ArgumentNullException(nameof(secureString));
}
var unmanagedString = IntPtr.Zero;
try
{
//copy secure string into unmanaged memory
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secureString);
//alloc managed string and copy contents of unmanaged string data into it
return Marshal.PtrToStringUni(unmanagedString);
}
finally
{
if (unmanagedString != IntPtr.Zero)
{
Marshal.FreeBSTR(unmanagedString);
}
}
}


}
}

在这里,我们使用 System 中的方法。运行时间。InteropServices 临时分配 BSTR (二进制非托管字符串) ,并确保释放临时使用的非托管内存。