All reservations about unsecuring your SecureString by creating a System.String out of it aside, how can it be done?
How can I convert an ordinary S
I think it would be best for SecureString dependent functions to encapsulate their dependent logic in an anonymous function for better control over the decrypted string in memory (once pinned).
The implementation for decrypting SecureStrings in this snippet will:
finally block.This obviously makes it a lot easier to "standardize" and maintain callers vs. relying on less desirable alternatives:
string DecryptSecureString(...) helper function.Notice here, you have two options:
static T DecryptSecureString which allows you to access the result of the Func delegate from the caller (as shown in the DecryptSecureStringWithFunc test method).static void DecryptSecureString is simply a "void" version which employ an Action delegate in cases where you actually don't want/need to return anything (as demonstrated in the DecryptSecureStringWithAction test method).Example usage for both can be found in the StringsTest class included.
Strings.cs
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace SecurityUtils
{
public partial class Strings
{
///
/// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
///
/// Generic type returned by Func delegate
/// Func delegate which will receive the decrypted password pinned in memory as a String object
/// Result of Func delegate
public static T DecryptSecureString(SecureString secureString, Func 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);
}
}
///
/// Runs DecryptSecureString with support for Action to leverage void return type
///
///
///
public static void DecryptSecureString(SecureString secureString, Action action)
{
DecryptSecureString(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(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);
}
}
}
Obviously, this doesn't prevent abuse of this function in the following manner, so just be careful not to do this:
[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
}
Happy coding!