Working with SecureString in .NET applications

The SecureString class is a .NET type that provides an increased level of security for sensitive in-memory data.

Having said that, the security benefits of SecureString have been widely debated. The general consensus is that SecureString can help to increase application security if used properly, however, the inherent advantages are somewhat limited in scope.

Despite this, there are situations where you’ll need to work with SecureString. Whether that is for a specific security scenario, or because a framework method or API expects a SecureString instance to be passed to it.

In this article, I look at how SecureString works and how to use it properly. I also look at how to convert SecureString to and from plain strings when the security of data in memory is not of particular concern to your application.

How does SecureString work?

SecureString is designed to store data more securely than the managed string type. It attempts to do this by leveraging the protection mechanisms of the underlying platform/OS, such as encryption, to avoid keeping data in memory as plain text for longer than is necessary.

String comparison

The string type is immutable. This means that once created it cannot be changed and therefore any ‘modifications’ that are made to a string are in fact creating copies of the string, rather than altering the original string instance. The value of a string type is stored in managed memory, making it easier to inspect its value. There are also no guarantees as to when a string will be disposed of by the garbage collector. This means that potentially sensitive data can hang around in memory as plain text for a long time and there is no way for an application to directly delete a string value from managed memory.

SecureString on the other hand, is a mutable type, until is it made read-only by calling its MakeReadOnly method. Being mutable helps to avoid multiple string values from being created while building the string. A SecureString instance is also pinned in memory and can be disposed of immediately by calling its Dispose method directly, or by using a language construct such as using in C#. Internally, the underlying value is stored as a char array that is encrypted, at least on Windows.

Caveats

The SecureString type offers “a measure of security” by reducing the time window during which a plain text version of the string is available in memory. It does not provide complete protection from attempts to access sensitive data.

Something also worth noting is that Microsoft doesn’t recommend using the SecureString class for new development, as highlighted in the SecureString shouldn’t be used document on GitHub.

The primary reason for this is that the benefits of SecureString are limited since .NET still has to decrypt to plain text when appending or removing characters from a SecureString instance. Additionally, even on Windows, the concept of SecureString doesn’t exist at the OS level, so there will always be occasions when sensitive data is in its plain-text form. As stated previously, SecureString merely reduces the window during which the plain text can be accessed rather than providing complete security.

When running .NET Core applications on non-Windows platforms, a SecureString cannot encrypt its underlying value due to issues such as “missing APIs or key management issues”. On these platforms, the SecureString implementation has to rely on other protection mechanisms that may not be as secure.

The alternative suggestion to using SecureString is to avoid credentials altogether and instead use other authentication methods such as certificates or Windows authentication. It goes without saying that this isn’t going to be practical advice for quite a number of applications. Therefore, it may still be worth considering using SecureString in certain situations to provide an additional measure of security, until a better option becomes available as a result of tighter OS integration or something else.

Before moving on, in case it matters to you, the maximum length of a SecureString value is 65,536 characters.

Example usage

A basic example of using the SecureString class as intended in a .NET Console application is as follows.

Note that you’ll need .NET 6 which includes the C# 10 compiler in order for the top-level statements style of code that is used below to compile.

using System.Diagnostics;
using System.Security;
 
using var secureString = new SecureString();
 
ConsoleKeyInfo key;
 
Console.Write("Enter password: ");
 
do
{
    // Read each character from the console
    // until the Enter key is pressed.
    key = Console.ReadKey(true);
 
    if (key.Key == ConsoleKey.Backspace)
    {
        // Remove the last character from the SecureString
        // if the Backspace key is pressed.
        if (secureString.Length > 0)
        {
            secureString.RemoveAt(secureString.Length - 1);
            Console.Write("\b \b");
        }
    }
    else
    {
        // Append the character to the SecureString.
        secureString.AppendChar(key.KeyChar);
        Console.Write("*");
    }
} while (key.Key != ConsoleKey.Enter);
 
Console.WriteLine();
 
try
{
    // Try to launch Notepad with the specified credentials.
    Process.Start("Notepad", "USERNAME", secureString, "DOMAIN");
}
catch (Exception ex)
{
    Console.WriteLine(ex);
}
 
Console.ReadKey(true);

In the above example, a password is being read one character at a time from the console using the static ReadKey method of the Console class, until the user presses the Enter key.

Note that it is key (no pun intended) for security that the SecureString value is built from an unmanaged source such as the ReadKey method where one character is being read at a time and therefore no unnecessary copies of the plain text are being allocated within managed memory.

The AppendChar method is used to append each character that is entered to the internal char array of the SecureString instance. Following this, an asterisk character is written to the console to indicate that the keystroke has been accepted.

If the Backspace key is pressed, the RemoveAt method is used to remove the last character that was entered. Following this, Backspace escape characters surrounding a space character are written out to remove the last asterisk character from the console.

After the user presses the Enter key, the code will break out of the do/while loop and attempt to launch a ‘Notepad’ process. The static Start method of the Process class features an overload that accepts a SecureString instance containing the password used to launch the process.

The SecureString instance will be disposed of at the end of the code block as a result of the using var syntax that was specified when creating the object instance.

Bear in mind that the above example is purposely designed to be as simple as possible and doesn’t handle edge cases around character entry and error handling etc. However, it provides a practical example of where a SecureString can be used when calling a method that belongs to the .NET BCL and should help you to understand the importance of appending one character at a time to the SecureString instance, if increasing security is your primary concern.

Aside from the Process class, other .NET types that use SecureString include NetworkCredential and SqlCredential.

Extension methods

If you are not concerned with security due to the nature of your application, it is possible to avail of some extension methods that will make life easier when converting a string instance to and from a SecureString instance.

An example of a possible scenario where you might need to convert a string to SecureString is when you are using a .NET API that requires a SecureString instance to be passed to it, but it isn’t practical for you to build up the instance value one character at a time from an unmanaged source.

Security Notice

It is very important to understand that by converting string to SecureString (and vice-versa) the security benefits in your code are immediately compromised. For example, if the data you are converting contains a password, then a plain text copy of the password will be hanging around in managed memory for an unpredictable length of time.

If you understand the above security notice and have resolved that this is not a concern for your application, you may consider using the code samples contained in the following sub-sections.

Convert to SecureString

It is possible to convert a string to a SecureString using the following code.

/// <summary>
/// Converts an unsecure string to a <see cref="SecureString"/>.
/// </summary>
/// <param name="unsecureString">The unsecure string to operate on</param>
/// <returns><see cref="SecureString"/></returns>
public static SecureString ToSecureString(this string unsecureString)
{
    if (unsecureString == null) return null;
 
    return unsecureString.Aggregate(
        new SecureString(),
        (s, c) => 
        { 
            s.AppendChar(c);
            return s;
        },
        (s) => 
        { 
            s.MakeReadOnly();
            return s;
        });
}

The above method is implemented as an extension method so that it can be conveniently called as if it was a normal string instance method.

Within the method body, the Aggregate method is used to build the SecureString object and associated value. This works because the String class implements IEnumerable<char>, allowing LINQ extension methods like Aggregate to be used.

The first Aggregate method parameter is the initial ‘accumulator’ value to operate on i.e. a newly created SecureString object.

The second Aggregate method parameter expects a Func delegate that takes the SecureString object that is being operated on as its first parameter and the next char in the string that is being iterated through as the second parameter. The Func returns the updated SecureString object each time it is invoked and this is passed along to the next accumulator invocation until all characters have been iterated through.

The third Aggregate method parameter also expects a Func delegate. This delegate is invoked after all of the accumulator invocations for each character have been made, in order to return the final value. Before returning, the MakeReadOnly method is called to prevent any further modifications to the underlying value.

Below is an example of using the ToSecureString extension method.

string unsecureString = "sensitive";
 
SecureString secureString = unsecureString.ToSecureString();
 
Console.WriteLine("SecureString Length: {0}", secureString.Length);
 
// Output: SecureString Length: 9

Before moving on, let me reemphasize that while the above code is convenient for converting an ‘unsecure’ string instance to a SecureString instance, the implementation of the method removes the security benefits associated with SecureString.

For a start, the string instance that is used to create the SecureString from will already be in managed memory. Additionally, each time the Func delegates are invoked by the Aggregate method, new string instances containing sensitive data will be created.

Convert to String

The SecureString class purposely doesn’t expose members that help to convert it to another type directly. This helps to protect its underlying value from unintended exposure.

If you need to convert a SecureString to a string the following code can be used.

/// <summary>
/// Converts a <see cref="SecureString"/> to an unsecure string.
/// </summary>
/// <param name="secureString">The <see cref="SecureString"/> to operate on</param>
/// <returns>An unsecure string</returns>
public static string ToUnsecureString(this SecureString secureString)
{
    if (secureString == null) return null;
 
    var unmanagedString = IntPtr.Zero;
 
    try
    {
        unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secureString);
        return Marshal.PtrToStringUni(unmanagedString);
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
    }
}

The above extension method uses static methods of the Marshal class to copy the contents of the SecureString object into unmanaged memory and then copies all of the characters back into a managed string object. 

The important part of the above code is the try/finally construct. The code in the finally block ensures that the unmanaged pointer to the string is freed to tidy things up in memory.

Again, as per the previous sub-section, it is important to be aware that converting a SecureString to a string will remove any and all security benefits. Nevertheless, there are most certainly scenarios where you have no choice but to use a plain string object so that you can pass data to an API or implement unit tests.

Summary

In this article, I have looked at what the SecureString type provided by .NET can offer in regards to application security.

I started by discussing how SecureString can provide a limited measure of protection for sensitive in-memory data and took a cursory glance at how it works behind the scenes.

I then looked at some example code for building up and using a SecureString instance using the recommended approach of building the underlying value one character at a time from an unmanaged source.

Lastly, I introduced extension methods that can be used for converting to and from SecureString, at the expense of any security benefits that would otherwise be realised.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments