Как преобразовать SecureString в System.String?
Как можно сделать все оговорки о том, как обезопасить вашу SecureString, создав из нее System.String?
Как я могу преобразовать обычный System.Security.SecureString в System.String?
Я уверен, что многие из вас, кто знаком с SecureString, ответят, что никогда не следует преобразовывать SecureString в обычную строку.NET, поскольку она удаляет все средства защиты. Я знаю Но сейчас моя программа все равно делает все с обычными строками, и я пытаюсь повысить ее безопасность, и хотя я собираюсь использовать API, который возвращает мне SecureString, я не пытаюсь использовать это для повышения своей безопасности.
Я знаю о Marshal.SecureStringToBSTR, но я не знаю, как взять этот BSTR и сделать из него System.String.
Для тех, кто может потребовать, чтобы узнать, почему я хотел бы это сделать, я беру пароль от пользователя и отправляю его в виде HTML-формы POST для входа пользователя на веб-сайт. Итак... это действительно должно быть сделано с помощью управляемых, незашифрованных буферов. Если бы я мог даже получить доступ к неуправляемому, незашифрованному буферу, я представлял бы, что я мог бы делать побитовую запись потока в сетевом потоке и надеяться, что это обеспечивает безопасность пароля на протяжении всего пути. Я надеюсь получить ответ хотя бы на один из этих сценариев.
13 ответов
Использовать 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);
}
}
Очевидно, вы знаете, как это разрушает всю цель SecureString, но я все равно переформулирую.
Если вы хотите использовать одну строку, попробуйте это: (только для.NET 4 и выше)
string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;
Где securePassword является SecureString.
Dang. сразу после публикации я нашел ответ глубоко в этой статье. Но если кто-то знает, как получить доступ к неуправляемому, незашифрованному буферу IntPtr, который предоставляет этот метод, по одному байту за раз, чтобы мне не приходилось создавать из него управляемый строковый объект для поддержания высокого уровня безопасности, добавьте ответ.:)
static String SecureStringToString(SecureString value)
{
IntPtr bstr = Marshal.SecureStringToBSTR(value);
try
{
return Marshal.PtrToStringBSTR(bstr);
}
finally
{
Marshal.FreeBSTR(bstr);
}
}
На мой взгляд, методы расширения являются наиболее удобным способом решения этой проблемы.
Я взял превосходный ответ Steve In 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;
}
}
Теперь вы можете просто конвертировать ваши строки назад и вперед так:
// 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
Но имейте в виду, что метод декодирования должен использоваться только для тестирования.
Я думаю, что это будет лучше для SecureString
зависимые функции для инкапсуляции их зависимой логики в анонимную функцию для лучшего контроля за дешифрованной строкой в памяти (после закрепления).
Реализация для расшифровки SecureStrings в этом фрагменте будет:
- Закрепите строку в памяти (это то, что вы хотите сделать, но, похоже, отсутствует в большинстве ответов здесь).
- Передайте свою ссылку делегату Func/Action.
- Вычистите это из памяти и выпустите GC в
finally
блок.
Это, очевидно, значительно упрощает "стандартизацию" и поддержку вызывающих абонентов по сравнению с использованием менее желательных альтернатив:
- Возвращение расшифрованной строки из
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
}
Удачного кодирования!
Я создал следующие методы расширения на основе ответа от 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;
});
}
Окончательное рабочее решение в соответствии с решением sclarke81 и исправлениями Джона Флаэрти:
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;
});
}
}
Код, принятый в качестве ответа, правильный и будет работать в большинстве случаев, но, как упоминалось в комментариях, использование BSTR было бы лучше и охватывало бы все обстоятельства:
private string SecureStringToString(SecureString value) {
IntPtr valuePtr = IntPtr.Zero;
try {
valuePtr = Marshal.SecureStringToBSTR(value);
return Marshal.PtrToStringBSTR(valuePtr);
} finally {
Marshal.ZeroFreeBSTR(valuePtr);
}
}
Я получил из этого ответа 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;
});
}
}
Используйте следующее:
var plaintextPwd = new System.Net.NetworkCredential("", <securestring with your encrypted password>).Password
Этот код 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;
}
}
}
// 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;
}
Если вы используете StringBuilder
вместо string
, вы можете перезаписать фактическое значение в памяти, когда вы закончите. Таким образом, пароль не будет храниться в памяти, пока сборщик мусора не заберет его.
StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());