Is SecureString ever practical in a C# application?

C#Security

C# Problem Overview


Feel free to correct me if my assumptions are wrong here, but let me explain why I'm asking.

Taken from MSDN, a SecureString:

> Represents text that should be kept confidential. The text is encrypted for privacy when being used, and deleted from computer memory when no longer needed.

I get this, it makes complete sense to store a password or other private information in a SecureString over a System.String, because you can control how and when it is actually stored in memory, because a System.String:

> is both immutable and, when no longer needed, cannot be programmatically scheduled for garbage collection; that is, the instance is read-only after it is created and it is not possible to predict when the instance will be deleted from computer memory. Consequently, if a String object contains sensitive information such as a password, credit card number, or personal data, there is a risk the information could be revealed after it is used because your application cannot delete the data from computer memory.

However, in the case of a GUI application (for example, an ssh client), the SecureString has to be built from a System.String. All of the text controls use a string as its underlying data type.

So, this means that every time the user presses a key, the old string that was there is discarded, and a new string is built to represent what the value inside the text box is, even if using a password mask. And we can't control when or if any of those values are discarded from memory.

Now it's time to log in to the server. Guess what? You need to pass a string over the connection for authentication. So let's convert our SecureString into a System.String.... and now we have a string on the heap with no way to force it to go through garbage collection (or write 0's to its buffer).

My point is: no matter what you do, somewhere along the line, that SecureString is going to be converted into a System.String, meaning it will at least exist on the heap at some point (without any guarantee of garbage collection).

My point is not: whether there are ways of circumventing sending a string to an ssh connection, or circumventing having a control store a string (make a custom control). For this question, you can replace "ssh connection" with "login form", "registration form", "payment form", "foods-you-would-feed-your-puppy-but-not-your-children form", etc.

  • So, at what point does using a SecureString actually become practical?
  • Is it ever worth the extra development time to completely eradicate the use of a System.String object?
  • Is the whole point of SecureString to simply reduce the amount of time a System.String is on the heap (reducing its risk of moving to a physical swap file)?
  • If an attacker already has the means for a heap inspection, then he most likely either (A) already has the means to read keystrokes, or (B) already physically has the machine... So would using a SecureString prevent him from getting to the data anyways?
  • Is this just "security through obscurity"?

Sorry if I'm laying the questions on too thick, curiosity just got the better of me. Feel free to answer any or all of my questions (or tell me that my assumptions are completely wrong). :)

C# Solutions


Solution 1 - C#

There are actually very practical uses of SecureString.

Do you know how many times I've seen such scenarios? (the answer is: many!):

  • A password appears in a log file accidentally.
  • A password is being shown at somewhere - once a GUI did show a command line of application that was being run, and the command line consisted of password. Oops.
  • Using memory profiler to profile software with your colleague. Colleague sees your password in memory. Sounds unreal? Not at all.
  • I once used RedGate software that could capture the "value" of local variables in case of exceptions, amazingly useful. Though, I can imagine that it will log "string passwords" accidentally.
  • A crash dump that includes string password.

Do you know how to avoid all these problems? SecureString. It generally makes sure you don't make silly mistakes as such. How does it avoid it? By making sure that password is encrypted in unmanaged memory and the real value can be only accessed when you are 90% sure what you're doing.

In the sense, SecureString works pretty easily:

  1. Everything is encrypted

  2. User calls AppendChar

  3. Decrypt everything in UNMANAGED MEMORY and add the character

  4. Encrypt everything again in UNMANAGED MEMORY.

What if the user has access to your computer? Would a virus be able to get access to all the SecureStrings? Yes. All you need to do is hook yourself into RtlEncryptMemory when the memory is being decrypted, you will get the location of the unencrypted memory address, and read it out. Voila! In fact, you could make a virus that will constantly scan for usage of SecureString and log all the activities with it. I am not saying it will be an easy task, but it can be done. As you can see, the "powerfulness" of SecureString is completely gone once there's a user/virus in your system.

You have a few points in your post. Sure, if you use some of the UI controls that hold a "string password" internally, using actual SecureString is not that useful. Though, still, it can protect against some stupidity I've listed above.

Also, as others have noted, WPF supports PasswordBox which uses SecureString internally through its SecurePassword property.

The bottom line is; if you have sensitive data(passwords, credit-cards, ..), use SecureString. This is what C# Framework is following. For example, NetworkCredential class stores password as SecureString. If you look at this, you can see over ~80 different usages in .NET framework of SecureString.

There are many cases when you have to convert SecureString to string, because some API expects it.

The usual problem is either:

  1. The API is GENERIC. It does not know that there's a sensitive data.
  2. The API knows that it's dealing with sensitive data and uses "string" - that's just bad design.

You raised good point: what happens when SecureString is converted to string? This can only happen because of the first point. E.g. the API does not know that it's sensitive data. I have personally not seen that happening. Getting string out of SecureString is not that simple.

It's not simple for a simple reason; it was never intended to let the user convert SecureString to string, as you stated: GC will kick in. If you see yourself doing that, you need to step back and ask yourself: Why am I even doing this, or do I really need this, why?

There's one interesting case I saw. Namely, the WinApi function LogonUser takes LPTSTR as a password, which means you need to call SecureStringToGlobalAllocUnicode. That basically gives you unencrypted password that lives in unmanaged memory. You need to get rid of that as soon as you're done:

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}
   

You can always extend the SecureString class with an extension method, such as ToEncryptedString(__SERVER__PUBLIC_KEY), which gives you a string instance of SecureString that is encrypted using server's public key. Only server can then decrypt it. Problem solved: Garbage Collection will never see the "original" string, as you never expose it in managed memory. This is exactly what is being done in PSRemotingCryptoHelper (EncryptSecureStringCore(SecureString secureString)).

And as something very almost-related: Mono SecureString does not encrypt at all. The implementation has been commented out because ..wait for it.. "It somehow causes nunit test breakage", which brings to my last point:

SecureString is not supported in everywhere. If the platform/architecture does not support SecureString, you'll get an exception. There's a list of platforms that are supported in the documentation.

Solution 2 - C#

The are few issue in your assumptions.

First of all the SecureString class does not have a String constructor. In order to create one you allocate an object and then append the chars.

In the case of a GUI or a console, you can very easily pass each pressed key to a secure string.

The class is designed in a way that you cannot, by mistake, access the value that is stored. This means that you can not obtain the string as a password directly from it.

So for using it, for example, to authenticate through the web, you will have to use proper classes that are also secure.

In the .NET framework you have a few classes that can use SecureString

  • WPF's PasswordBox control keeps the password as a SecureString internally.
  • System.Diagnostics.ProcessInfo's Password property is a SecureString.
  • The constructor for X509Certificate2 takes a SecureString for the password.

(more)

To conclude, the SecureString class can be useful, but requires more attention from the developer.

All this, with examples, is well described in MSDN's documentation of SecureString

Solution 3 - C#

A SecureString is useful if:

  • You build it character by character (e.g. from console input) or obtain it from an unmanaged API

  • You use it by passing it to an unmanaged API (SecureStringToBSTR).

If you ever convert it to a managed string, you've defeated its purpose.

UPDATE in response to comment

> ... or BSTR like you mention, which doesn't seem any more secure

After its been converted to a BSTR, the unmanaged component that consumes the BSTR can zero the memory. Unmanaged memory is more secure in the sense that it can be reset in this way.

However, there are very few, APIs in the .NET Framework that support SecureString, so you're right to say that it's of very limited value today.

The main use case I would see is in a client application that requires the user to enter a highly sensitive code or password. The user input could be used character by character to build a SecureString, then this could be passed to an unmanaged API, which zeroes the BSTR it receives after using it. Any subsequent memory dump will not contain the sensitive string.

In a server application it's hard to see where it would be useful.

UPDATE 2

One example of a .NET API that accepts a SecureString is http://msdn.microsoft.com/en-us/library/ms148390(v=vs.110).aspx">this constructor for the X509Certificate class. If you spelunk with ILSpy or similar, you'll see that the SecureString is internally converted to an unmanaged buffer (Marshal.SecureStringToGlobalAllocUnicode), which is then zeroed when finished with (Marshal.ZeroFreeGlobalAllocUnicode).

Solution 4 - C#

Microsoft does not recommend to use SecureString for newer codes.

From documentation of SecureString Class:

> Important > > We don't recommend that you use the SecureString class for new > development. For more information, see SecureString shouldn't be used

Which recommends:

> Don't use SecureString for new code. When porting code to .NET Core, > consider that the contents of the array are not encrypted in memory. > > The general approach of dealing with credentials is to avoid them and > instead rely on other means to authenticate, such as certificates or > Windows authentication. > on GitHub.

Solution 5 - C#

As you have correctly identified, SecureString offers one specific advantage over string: deterministic erasure. There are two problems with this fact:

  1. As others have mentioned and as you have noticed by yourself, this isn't enough by itself. You have to make sure that every step of the process (including retrieval of input, construction of the string, usage, deletion, transportation, etc) happens without defeating the purpose of using SecureString. This means that you must be careful to never create a GC-managed immutable string or any other buffer that will store the sensitive information (or you'll have to keep track of that as well). In practice, this isn't always easy to achieve, because lots of APIs only offer a way to work with string, not SecureString. And even if you do manage to everything right...
  2. SecureString protects against very specific kinds of attack (and for some of them, it's not even that reliable). For example, SecureString does allow you to shrink the time window in which an attacker can dump the memory of your process and successfully extract the sensitive information (again, as you correctly pointed out), but hoping that the window is too small for the attacker to take a snapshot of your memory isn't considered security at all.

So, when should you use it? Only when you're working with something that can allow you to work with SecureString for all your needs and even then you should still be mindful that this is secure only in specific circumstances.

Solution 6 - C#

Below text is copied from HP Fortify static code analyzer

Abstract: The method PassString() in PassGenerator.cs stores sensitive data in an insecure manner (i.e. in string), making it possible to extract the data via inspecting the heap.

Explanation: Sensitive data (such as passwords, social security numbers, credit card numbers etc.) stored in memory can be leaked if it is stored in a managed String object. String objects are not pinned, so the garbage collector can relocate these objects at will and leave several copies in memory. These objects are not encrypted by default, so anyone who can read the process' memory will be able to see the contents. Furthermore, if the process' memory gets swapped out to disk, the unencrypted contents of the string will be written to a swap file. Lastly, since String objects are immutable, removing the value of a String from memory can only be done by the CLR garbage collector. The garbage collector is not required to run unless the CLR is low on memory, so there is no guarantee as to when garbage collection will take place. In the event of an application crash, a memory dump of the application might reveal sensitive data.

Recommendations: Instead of storing sensitive data in objects like Strings, store them in a SecureString object. Each object stores its contents in an encrypted format in memory at all times.

Solution 7 - C#

I'd like to address this point:

> If an attacker already has the means for a heap inspection, then they most likely either (A) already have the means to read keystrokes, or (B) already physically have the machine... So would using a SecureString prevent them from getting to the data anyways?

An attacker may not have full access to the computer and the application but can have means to access some parts of the memory of the process. It is usually caused by bugs like buffer overruns when specially constructed input can cause the application to expose or overwrite some part of the memory.

HeartBleed leaking memory

Take Heartbleed for example. Specially constructed requests can cause the code to expose random parts of the memory of the process to the attacker. An attacker can extract SSL certificates from the memory, yet the only thing he needs is just to use a malformed request.

In the world of the managed code, buffer overruns become a problem much less often. And in case of WinForms, data is already stored in an insecure manner and you can't do anything about it. This renders the protection with SecureString pretty much useless.

However, GUI can be programmed to use SecureString, and in such case reducing the window of password availability in the memory can be worth it. For example, PasswordBox.SecurePassword from WPF is of type SecureString.

Solution 8 - C#

Some time ago I had to create a c# interface against a java credit card payment gateway and one needed a compatible secure communications key encryptions. As the Java implementation was rather specific and I had to work with the protected data in a given way.

I found this design to be quite easy to use and easyer than working with SecureString… for those that like to use… feel free, no legal restrictions :-). Note that these classes are internal, you might need to make them public.

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);
                    
                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;
                
                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSteven JeffriesView Question on Stackoverflow
Solution 1 - C#Erti-Chris EelmaaView Answer on Stackoverflow
Solution 2 - C#Damian Leszczyński - VashView Answer on Stackoverflow
Solution 3 - C#JoeView Answer on Stackoverflow
Solution 4 - C#SᴇMView Answer on Stackoverflow
Solution 5 - C#Theodoros ChatzigiannakisView Answer on Stackoverflow
Solution 6 - C#vikrantxView Answer on Stackoverflow
Solution 7 - C#AthariView Answer on Stackoverflow
Solution 8 - C#Walter VerhoevenView Answer on Stackoverflow