8

The use case is to protect strings in memory programming in c#. The use of the class SecureString (https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netframework-4.7.2) is discouraged by Microsoft itself.

I was wondering if it could be a valid alternative to:

  • transform the string in a byte array and immediately set the string to null (and eventually call the garbage collector),
  • encrypt the byte array with the class ProtectedMemory.

Any suggestion?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Francesca Merighi
  • 317
  • 1
  • 3
  • 10
  • 6
    Why? What is the actual problem you want to solve? What do you want to protect against? If someone has debugging permissions on your server, is able to take memory dumps and inspect memory contents, you've already lost. The reason Microsoft recommends against using SecureString is that *all* solutions will keep the string as cleartext in memory for some time, no matter how small. Even if you encrypt the string, it has to start as cleartext before it can get encrypted – Panagiotis Kanavos Apr 09 '19 at 10:44
  • 3
    @PanagiotisKanavos "Even if you encrypt the string, it has to start as cleartext before it can get encrypted" - this is untrue. `SecureString.Append` means you can construct a secure string from user input (e.g. for each keypress) without needing to store cleartext. – Dai Apr 09 '19 at 14:56
  • 2
    @Dai and that user input is cleartext. Entering input a keypress at a time doesn't even full keyloggers. SecureString offers limited protection, for a limited scope. It's not meant to secure the input process. *Using* that string requires extracting the contents too – Panagiotis Kanavos Apr 10 '19 at 07:37
  • @PanagiotisKanavos The reason the the credit card number was cleartext before it got encrypted is because the user typed it in as plaintext. We want to be sure to get that out of memory as quickly as possible. If the argument is that we should never bother: that's fine. But Windows still zero's memory before handing you a a 4KB page (so you can't see what was on the page before), and it still has `SecureZeroMemory`. – Ian Boyd Apr 11 '21 at 17:31
  • @IanBoyd the argument is that the .NET team says SecureString isn't secure and offers *very* limited security, while giving the illusion of security. The original credit card text will be in memory until it's GC'd. And once you use the Windows API, or anything that doesn't understand SecureString, you get the string back. – Panagiotis Kanavos Apr 12 '21 at 06:05

4 Answers4

9

There is no alternative to the SecureString class. The 'alternative' Microsoft encourages is found here:

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.

So, if you really need the credentials and there is no other way: on .NET Framework, use SecureString. For .NET Core there is no alternative at the moment.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
7

I wouldn't say it's "discouraged by Microsoft" - that's an oversimplification. The actual reasons are given in this page ( https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md ) and the argument seems to be "it isn't worth the effort to use it in .NET Core", and not that it isn't secure overall.

I contend that SecureString is secure... but only for the .NET Framework on Windows. The page I linked to is from the .NET Core project which is cross-platform - so it makes sense to discourage or disallow the use of SecureString in .NET Core - but if your project is targeting .NET Framework (which is exclusive to Windows) or is targeting .NET Core for Windows - then you're fine. The quote is below (emphasis mine):

The contents of the array is unencrypted except on .NET Framework.

BTW, SecureString can be used securely to avoid cleartext in memory if you only read secrets directly into the SecureString directly by using its Append method. This is most useful when reading passwords from the console (pseudocode):

Console.WriteLine( "Enter your password" );
SecureString password = new SecureString();
while( Char c = Console.ReadKey() != '[Enter'] ) {
    password.Append( c );
}

...however if you need access to the cleartext version of the string afterwards then it's less secure (though the cleartext string would hopefully be collected by GC as a Generation 0 object).

Regarding your proposal:

  • transform the string in a byte array and immediately set the string to null (and eventually call the garbage collector)
  • encrypt the byte array with the class ProtectedMemory.

This is exactly how SecureString works already, and it still suffers from the same problems: the cleartext copy of the encrypted contents still exists in memory for a short period of time - that's the problem.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Hi Dai. Can you tweak this small discrepancy I see in your answer. "I contend that SecureString is secure... but only for the .NET Framework on Windows." but also "or is targeting .NET Core for Windows - then you're fine". So i think we all agree that dotnetcore on linux...is the "not as good as we would hope". but dotnetCORE ON windows..is the fork in the road. – granadaCoder Apr 29 '20 at 21:29
  • I think some of the issues around this is I think there are three, not two permutations. The "2" "dotnet framework" vs "dot net core". But I think there 3 permtuations. (1) .NET FW for Windows (2) .NET Core for (running on) Windows (3) .NET Core not-for (not-running-on) Windows. And I think (but am asking your opinion and my early request about the discrepancy) is that "(2) .NET Core for (running on) Windows" still provides value with SecureString (IF USED PROPERLY OF COURSE). I've said for 10 years (before .net core existed) that m$ should have named it "KindaSecureString" :) – granadaCoder Apr 29 '20 at 21:31
  • @granadaCoder There's no discrepancy or contradiction in my answer: provided your `SecureString` code is used on Windows, it's secure-as-can-be - compared to not being secure at all on Linux and macOS. – Dai Apr 29 '20 at 21:31
  • I agree with the comment you just posted. However, your answer says " but only for the .NET Framework on Windows. " (emphasis on the "only" and "framework on windows"). That's where I see a discrepancy. – granadaCoder Apr 29 '20 at 21:32
  • @granadaCoder I qualified it with ".NET Framework for Windows" because prior to .NET Core being released in 2016 there were (and are) other platforms also called ".NET Framework" for non-Windows platforms such as .NET Micro Framework, .NET/XNA for Xbox, .NET Compact Framework, Xamarin/Mono (before .NET Core was released), and others. – Dai Apr 29 '20 at 21:35
  • @granadaCoder Prior to .NET Core being released, people used the term ".NET Framework" to refer to using *any* .NET implementation, not just the official/main .NET Framework for desktop Windows - just like how Java devs will use the generic term "JVM" to possibly refer to any Java environment, not just the official Sun/Oracle JVM. – Dai Apr 29 '20 at 21:37
  • Hey. What I am saying is that if someone reads "but only for the .NET Framework on Windows" and stopped right there, they would say "AHA ! secure string on dotnet core is not secure". But if they/me read further, then you see " hmm, dotnet core ON windows" is secure. i think it might be "but (when considering dotnetFW)...(secure-string) is only for the .NET Framework on Windows". and then that would let people read further about the dotnet-core-on-windows permutation. i understand what you're saying about mono/etc. i'm just asking that it be a tad clearer on the dotnet-core-on-windows – granadaCoder Apr 29 '20 at 21:43
  • 1
    This has nothing to do with Windows. The .NET team doesn't recommend SecureString because it gives a false sense of security and not much of the real thing.[...we simply don't believe it has any protection in the real world. At some point, to get used, it gets turned back into a string and all bets are off. We haven't recommended it in years](https://twitter.com/blowdart/status/1379145789407039490). That's because [SecureString isn't a windows concept, so once you went down to a Windows API it was converted back.](https://twitter.com/blowdart/status/1379146228047351809) – Panagiotis Kanavos Apr 12 '21 at 06:22
  • And even SDK classes don't use it correctly. The only things that did : [PowerShell interactive input, character by character. Oh and Winforms managed it at one point, again, interactive character by character input. Nothing more. As for SDK functions, the only thing that used it well was NetworkCredentials. Everything else converted to strings.](https://twitter.com/blowdart/status/1379146105049423872) – Panagiotis Kanavos Apr 12 '21 at 06:25
  • If the .NET team thought SecureString makes sense they'd have no problem porting it. They recommend against it for years though, which is why it wasn't ported to .NET Core, not the other way around. It's actually considered harmful. From the GH issue that obsoleted the type [Contrast this against SecureString, where the existence of the type is causing active harm to customers who use it and subsequently fail audits.](https://github.com/dotnet/runtime/issues/30612) – Panagiotis Kanavos Apr 12 '21 at 06:35
7

tl;dr: SecureString is a good and right idea. The reason Microsoft no longer recommends it is because .NET Core can't have it, because Linux doesn't support encryption.

Short Answer

  • Use an Array of Char
  • Encrypt it

Long Answer

There are two reasons you want SecureString.

The first is along the lines of the HeartBleed attack, and the reason SecureZeroMemory exists, and the reason Windows always zeros a page of memory before giving it to you: to avoid leaking information.

Bonus reading

Terminal Services keeps user passwords unencrypted in RAM.

The second, and the reason SecureString exists in .NET, is because you have no way to call SecureZeroMemory. Strings in .NET are immutable, and you don't get to control their lifetime.

So in order to solve the problem there are two elements:

  1. You can switch to an array of Char.

When it is an array, it means you can wipe the contents. This means you can do the moral equivalent of ZeroMemory when you're done with the sensitive credit card number, bitcoin private key, password, Intel Blu-Ray Master Key: you can wipe them

That solves the problem of being completely unable to wipe a C# String

  1. Encrypt with CryptProtectData

The other, unrelated, virtue of SecureString is that the raw strings will not appear in memory dumps, virtual machine RAM snapshots, web-server logs, debugger watch windows. Or in the case of the HeartBleed attack, will not appear in uninitialized data handed out to other web-site users.

These are the two core elements that SecureString provides.

And both you can replicate. The reason .NET Core doesn't have them is not because SecureString is a bad idea, or a wasted idea, or an incomplete idea, or "doesn't deliver what it promises". Instead it's because Linux doesn't have the equivalent of CryptProtectData. And since .NET Core has to be cross-platform, they have to cater to the lowest common denominator. So they throw up their hands and say remove it.

But SecureString is just as valid as a concept as:

  • ZeroMemory
  • SecureZeroMemory
  • Windows zero'ing out a page of RAM before giving it to your process
  • why when you SetFileValidData, Windows zero's out the file contents

And anyone saying otherwise is flat out lying.

Reminder - you want to use SecureString

You want to use SecureString

Does it mean that the credit card number was in plaintext in memory at some point:

No, the reason is that it has very limited use and in most cases, the string either remains in memory, or re-appears. The original string remains in memory until it's GCd. And since SecureString has no counterpart in the Win32 API (or Linux) the original string will reappear once the app tries to do anything with it. Even in the SDK only NetworkCredentials used it properly and SecureString isn't a windows concept, so once you went down to a Windows API it was converted back.

  • Yes, the Credit Card number was in plaintext in memory at some point.
  • Yes, the user's bitlocker password was in memory at some point.
  • Yes, the iOS user's PIN was in memory at some point.
  • Yes, the BitCoin private key was in memory at some point.
  • Yes, the data in the encrypted pagefile was unencrypted in RAM at some point.

But what we have to realize is:

  • That doesn't mean you shouldn't wipe it from memory
  • that doesn't mean you shouldn't prevent it from appearing in a logfile
  • that doesn't mean you shouldn't prevent it from appearing in a swapfile
  • that doesn't mean you shouldn't prevent it from appearing in a crash dump file
  • that doesn't mean you shouldn't prevent it from appearing in a watch window
  • that doesn't mean you shouldn't prevent it from appearing in a snapshot

These are good defense-in-depth measures.

And anyone saying differently is simply wrong.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 2
    No, the reason is that it has very limited use and in most cases, the string either remains in memory, or re-appears. The original string remains in memory until it's GCd. And since SecureString has no counterpart in the Win32 API (or Linux) the original string will reappear once the app tries to do anything with it. Even in the SDK [only NetworkCredentials used it properly](https://twitter.com/blowdart/status/1379146105049423872) and `SecureString isn't a windows concept, so once you went down to a Windows API it was converted back.` – Panagiotis Kanavos Apr 12 '21 at 06:11
  • 4
    Microsoft didn't port SecureString to Linux because it's not recommended, not the other way around. `Shawn we simply don't believe it has any protection in the real world. At some point, to get used, it gets turned back into a string and all bets are off. We haven't recommended it in years.` [tweet](https://twitter.com/blowdart/status/1379145789407039490) – Panagiotis Kanavos Apr 12 '21 at 06:12
  • 1
    In fact, SecureString is considered harmful compared to other legacy types. [Contrast this against SecureString, where the existence of the type is causing active harm to customers who use it and subsequently fail audits.](https://github.com/dotnet/runtime/issues/30612) (From the GH issue to obsolete the class) – Panagiotis Kanavos Apr 12 '21 at 06:33
  • @PanagiotisKanavos If there really are people who think `SecureString` is bad, then we can create a new class that: a) stores the string in unmanaged memory, b) encrypts it with `CryptProtectMemory`. We can call this class `ProtectedString`. This way anyone whining about SecureString can be wrong **and** satisfied that we've done what they asked. Bonus, *".NET doesn't support encryption in all environments, either due to missing APIs or key management issues."* Which is a fault of other environments, not people trying to **use** encryption. – Ian Boyd Apr 12 '21 at 19:04
5

So the basic question you are asking is "since Microsoft discourages the user of SecureString, can I roll my own?".

Well, apart from the fact that your implementation will likely be less secure than Microsoft's original version, it will also at least share the same problems, because they are not with the specific implementation, but with the concept.

If you want to use the concept, you could as well use SecureString. The solution is to not use the concept of encrypted credentials in memory, neither with Microsoft's classes, nor with your own homebrew.

TylerH
  • 20,799
  • 66
  • 75
  • 101
nvoigt
  • 75,013
  • 26
  • 93
  • 142