Ich denke, es wäre am besten, wenn SecureString
abhängige Funktionen kapseln würden ihre abhängige Logik in einer anonymen Funktion um die entschlüsselte Zeichenfolge im Speicher (einmal fixiert) besser kontrollieren zu können.
Die Implementierung zum Entschlüsseln von SecureStrings in diesem Snippet wird:
- Pin die Zeichenfolge im Speicher (was Sie tun möchten, aber in den meisten Antworten hier zu fehlen scheint).
- Pass seine Referenz auf die Func / Aktionsdelegate.
- Scrub es aus dem Speicher und lassen Sie den GC im
finally
Block los.
Dies macht es offensichtlich viel einfacher, Anrufer zu "standardisieren" und zu warten, als sich auf weniger wünschenswerte Alternativen zu verlassen:
- Rückgabe der entschlüsselten Zeichenfolge von einer
string DecryptSecureString(...)
Hilfsfunktion.
- Duplizieren dieses Codes, wo immer er benötigt wird.
Beachten Sie, dass Sie hier zwei Möglichkeiten haben:
static T DecryptSecureString<T>
Hiermit können Sie Func
vom Aufrufer aus auf das Ergebnis des Delegaten zugreifen (wie in der DecryptSecureStringWithFunc
Testmethode gezeigt ).
static void DecryptSecureString
ist einfach eine "void" -Version, die einen Action
Delegaten in Fällen beschäftigt, in denen Sie tatsächlich nichts zurückgeben möchten / müssen (wie in der DecryptSecureStringWithAction
Testmethode gezeigt).
Eine Beispielverwendung für beide finden Sie in der enthaltenen StringsTest
Klasse.
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);
}
}
}
Dies verhindert natürlich nicht den Missbrauch dieser Funktion auf folgende Weise. Achten Sie also darauf, dies nicht zu tun:
[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
}
Viel Spaß beim Codieren!