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.
Comments