Toutes les réservations concernant la non sécurisation de votre SecureString en créant un System.String hors de celui-ci à part, comment peut-on le faire?
Comment puis-je convertir un System.Security.SecureString ordinaire en System.String?
Je suis sûr que beaucoup d’entre vous qui connaissent bien SecureString vont répondre qu’il ne faut jamais transformer un SecureString en une chaîne .NET ordinaire, car elle supprime toutes les protections de sécurité. Je sais. Mais pour le moment, mon programme fait tout avec des chaînes ordinaires de toute façon, et j'essaie d'améliorer sa sécurité. Même si je vais utiliser une API qui me renvoie un SecureString, je ne suis pas essayer d'utiliser cela pour augmenter ma sécurité.
Je suis au courant de Marshal.SecureStringToBSTR, mais je ne sais pas comment utiliser ce BSTR et en faire un System.String.
Pour ceux qui voudraient savoir pourquoi je voudrais faire cela, eh bien, je prends un mot de passe d’un utilisateur et le soumets sous forme de formulaire html POST pour connecter l’utilisateur à un Si vous pouviez accéder au tampon non géré et non crypté, j’imagine que je pourrais écrire du flux octet par octet sur le flux réseau et espérer que Je souhaite une réponse à au moins un de ces scénarios.
Utilisez la classe 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);
}
}
Si vous souhaitez éviter de créer un objet chaîne géré, vous pouvez accéder aux données brutes à l'aide de 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);
}
}
Évidemment, vous savez comment cela va à l’encontre du but d’un SecureString, mais je le reformulerai quand même.
Si vous voulez un one-liner, essayez ceci: (.NET 4 et versions ultérieures uniquement)
string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;
Où securePassword est un SecureString.
Dang. right Après avoir posté ceci, j'ai trouvé la réponse au plus profond de cet article . Mais si quelqu'un sait comment accéder au tampon IntPtr non géré, non chiffré que cette méthode expose, octet par octet, de sorte que je n'ai pas à en créer un objet chaîne géré pour conserver une sécurité élevée, ajoutez une réponse. :)
static String SecureStringToString(SecureString value)
{
IntPtr bstr = Marshal.SecureStringToBSTR(value);
try
{
return Marshal.PtrToStringBSTR(bstr);
}
finally
{
Marshal.FreeBSTR(bstr);
}
}
Je pense qu'il serait préférable que SecureString
fonctions dépendantes encapsulent leur logique dépendante dans une fonction anonyme pour un meilleur contrôle de la chaîne déchiffrée en mémoire (une fois épinglée).
L’implémentation de déchiffrement de SecureStrings dans cet extrait va:
finally
.Cela facilite évidemment la "normalisation" et la maintenance des appelants par rapport aux autres solutions moins souhaitables:
string DecryptSecureString(...)
.Remarquez ici, vous avez deux options:
static T DecryptSecureString<T>
qui permet d’accéder au résultat du délégué Func
de l’appelant (comme indiqué dans la méthode de test DecryptSecureStringWithFunc
.).static void DecryptSecureString
est simplement une version "void" qui emploie un délégué Action
dans les cas où vous ne voulez réellement/devez rien retourner (comme le montre la méthode de test DecryptSecureStringWithAction
.).Un exemple d'utilisation pour les deux se trouve dans la classe StringsTest
incluse.
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);
}
}
}
Évidemment, cela n'empêche pas l'utilisation abusive de cette fonction de la manière suivante, veillez donc à ne pas le faire:
[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
}
Bon codage!
À mon avis, les méthodes d'extension sont le moyen le plus pratique de résoudre ce problème.
J'ai pris Steve dans CO'sexcellente réponse et l'ai placé dans une classe d'extension comme suit, avec une deuxième méthode que j'ai ajoutée pour prendre en charge l'autre direction (chaîne -> chaîne sécurisée) vous pouvez également créer une chaîne sécurisée et la convertir ensuite en chaîne normale:
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;
}
}
Avec cela, vous pouvez maintenant simplement convertir vos chaînes d'avant en arrière comme ceci:
// 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
Mais gardez à l'esprit que la méthode de décodage ne devrait être utilisée que pour les tests.
J'ai créé les méthodes d'extension suivantes en fonction de réponse de rdev5 . Il est important d'épingler la chaîne gérée, car elle empêche le ramasse-miettes de la déplacer et de laisser des copies que vous ne pouvez pas effacer.
Je pense que l’avantage de ma solution est qu’aucun code non sécurisé n’est nécessaire.
/// <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;
});
}
En utilisant certains des exemples ci-dessus, je l'ai rendu plus évident pour mon cas plutôt que comme un rappel de délégué fonctionnel, bien sûr, il appartient au développeur de s'en départir.
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
}
Utilisation (gardez à l’esprit que la référence sera également.)
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
Ce code C # est ce que vous voulez.
%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;
}
}
}