How to convert SecureString to System.String?
Solution 1
Use the System.Runtime.InteropServices.Marshal
class:
String SecureStringToString(SecureString value) {
IntPtr valuePtr = IntPtr.Zero;
try {
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
return Marshal.PtrToStringUni(valuePtr);
} finally {
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
If you want to avoid creating a managed string object, you can access the raw data using Marshal.ReadInt16(IntPtr, Int32)
:
void HandleSecureString(SecureString value) {
IntPtr valuePtr = IntPtr.Zero;
try {
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
for (int i=0; i < value.Length; i++) {
short unicodeChar = Marshal.ReadInt16(valuePtr, i*2);
// handle unicodeChar
}
} finally {
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
Solution 2
Obviously you know how this defeats the whole purpose of a SecureString, but I'll restate it anyway.
If you want a one-liner, try this: (.NET 4 and above only)
string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;
Where securePassword is a SecureString.
Solution 3
Dang. right after posting this I found the answer deep in this article. But if anyone knows how to access the IntPtr unmanaged, unencrypted buffer that this method exposes, one byte at a time so that I don't have to create a managed string object out of it to keep my security high, please add an answer. :)
static String SecureStringToString(SecureString value)
{
IntPtr bstr = Marshal.SecureStringToBSTR(value);
try
{
return Marshal.PtrToStringBSTR(bstr);
}
finally
{
Marshal.FreeBSTR(bstr);
}
}
Solution 4
In my opinion, extension methods are the most comfortable way to solve this.
I took Steve in CO's excellent answer and put it into an extension class as follows, together with a second method I added to support the other direction (string -> secure string) as well, so you can create a secure string and convert it into a normal string afterwards:
public static class Extensions
{
// convert a secure string into a normal plain text string
public static String ToPlainString(this System.Security.SecureString secureStr)
{
String plainStr=new System.Net.NetworkCredential(string.Empty, secureStr).Password;
return plainStr;
}
// convert a plain text string into a secure string
public static System.Security.SecureString ToSecureString(this String plainStr)
{
var secStr = new System.Security.SecureString(); secStr.Clear();
foreach (char c in plainStr.ToCharArray())
{
secStr.AppendChar(c);
}
return secStr;
}
}
With this, you can now simply convert your strings back and forth like so:
// create a secure string
System.Security.SecureString securePassword = "MyCleverPwd123".ToSecureString();
// convert it back to plain text
String plainPassword = securePassword.ToPlainString(); // convert back to normal string
But keep in mind the decoding method should only be used for testing.
Solution 5
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:
- Pin the string in memory (which is what you want to do but appears to be missing from most answers here).
- Pass its reference to the Func/Action delegate.
- Scrub it from memory and release the GC in the
finally
block.
This obviously makes it a lot easier to "standardize" and maintain callers vs. relying on less desirable alternatives:
- Returning the decrypted string from a
string DecryptSecureString(...)
helper function. - Duplicating this code wherever it is needed.
Notice here, you have two options:
-
static T DecryptSecureString<T>
which allows you to access the result of theFunc
delegate from the caller (as shown in theDecryptSecureStringWithFunc
test method). -
static void DecryptSecureString
is simply a "void" version which employ anAction
delegate in cases where you actually don't want/need to return anything (as demonstrated in theDecryptSecureStringWithAction
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
{
/// <summary>
/// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
/// </summary>
/// <typeparam name="T">Generic type returned by Func delegate</typeparam>
/// <param name="action">Func delegate which will receive the decrypted password pinned in memory as a String object</param>
/// <returns>Result of Func delegate</returns>
public static T DecryptSecureString<T>(SecureString secureString, Func<string, T> 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);
}
}
/// <summary>
/// Runs DecryptSecureString with support for Action to leverage void return type
/// </summary>
/// <param name="secureString"></param>
/// <param name="action"></param>
public static void DecryptSecureString(SecureString secureString, Action<string> action)
{
DecryptSecureString<int>(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<bool>(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!
Related videos on Youtube
Andrew Arnott
Loving all things .NET: C#, ASP.NET, WCF, WPF. Loving all things identity: OpenID, OAuth, Cardspace, X.509. Appreciate my answers? Please donate bitcoins to 15j5QJNfvHkWaEFJBDQjVjoA2828mQE7Wr
Updated on February 19, 2022Comments
-
Andrew Arnott about 2 years
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 System.Security.SecureString to System.String?
I'm sure many of you who are familiar with SecureString are going to respond that one should never transform a SecureString to an ordinary .NET string because it removes all security protections. I know. But right now my program does everything with ordinary strings anyway, and I'm trying to enhance its security and although I'm going to be using an API that returns a SecureString to me I am not trying to use that to increase my security.
I'm aware of Marshal.SecureStringToBSTR, but I don't know how to take that BSTR and make a System.String out of it.
For those who may demand to know why I would ever want to do this, well, I'm taking a password from a user and submitting it as an html form POST to log the user into a web site. So... this really has to be done with managed, unencrypted buffers. If I could even get access to the unmanaged, unencrypted buffer I imagine I could do byte-by-byte stream writing on the network stream and hope that that keeps the password secure the whole way. I'm hoping for an answer to at least one of these scenarios.
-
John Suit over 9 yearsGot my up-vote too even years later, thanks for the help! Just a quick note: this also works as a static, in its own memory.
-
beterthanlife over 9 yearsAlthough it does defeat the purpose in production, your solution is perfect for unit tests. Thanks.
-
Ben Voigt over 8 yearsThis answer has a memory leak.
-
radbyx over 8 yearsI used
StopWatch
andSecureStringToString
took 4.6sec to run. It's to slow for me. Does anyone get the same time or something faster? -
Rasmus Faber over 8 years@radbyx In a quick and dirty test setup, I can call it 1000 times in 76ms. The first invocation takes 0.3 ms and subsequent invocations ~0.07ms. How large is your secure string and which version of the framework are you using?
-
radbyx over 8 yearsLength om my secureString is 168. I am using .NET Framework 3.5 if that answered your question? I have tryed 5-10 times is always around 4.5-4.65 sec~ I would love to get your time
-
radbyx over 8 years@RasmusFaber My bad, I had added a
Database.GetConnectionString()
into your code, to get my secureString, which was the evil part that took almost 5sec(and yes I should look into that! :) Your code took .00 mili seconds in my stopwatch so it's all good. Thanks for pointing me in the right direction. -
granadaCoder over 8 yearsThis helped me to figure out that a SecureString (System.Security.SecureString) was not being passed to my ApiController (webapi). Thx
-
El Ronnoco almost 8 years@BenVoigt Can you explain further please how this has a memory leak?
-
Ben Voigt almost 8 years@ElRonnoco: Nothing frees the
BSTR
explicitly, and it's not a .NET object so the garbage collector doesn't take care of it either. Compare to stackoverflow.com/a/818709/103167 which was posted 5 years earlier and doesn't leak. -
Ben Voigt almost 8 yearsWhile this is true, the garbage collector may still move the StringBuilder buffer around in memory during generational compaction, which makes the "overwrite the actual value" fail, because there is another (or more) leftover copy that isn't destroyed.
-
Ben Voigt almost 8 yearsYou can certainly use the
unsafe
keyword and achar*
, just callbstr.ToPointer()
and cast. -
Jay Sullivan almost 8 yearsThis doesn't even remotely answer the question.
-
jp2code over 7 yearsI used the exact code in this answer. Every time I pass in a SecureString value,
Marshal.PtrToStringUni
returns an Empty String. Any idea why or how to find out what is going wrong? -
stijn almost 7 yearsNote in PowerShell this is
[System.Net.NetworkCredential]::new('', $securePassword).Password
-
Dai over 5 yearsThe code should use
SecureStringToBSTR
becauseSecureString
can contain\0
as non-terminating characters, butSecureStringToGlobalAllocUnicode
treat it as a null-terminated string. -
Maximilian Burszley over 5 years@stijn Just being pedantic, but more accurately:
[System.Net.Credential]::new([string]::Empty, $securePassword).Password
. Even so, that requires version 5 to work:(New-Object Net.Credential [string]::Empty, $securePassword).Password
-
stijn over 5 years@TheIncorrigible1 can you elaborate? E.g. when is
''
not the same type as[String]::Empty
? AlsoNew-Object Net.Credential
doesn't work for me: Cannot find type [Net.Credential]: verify that the assembly containing this type is loaded -
sclarke81 over 5 yearsWhy not use
Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);
instead of thefixed
section? -
Jins Peter almost 5 yearswhy does this defeat the purpose? I don't understand.?
-
Steve In CO over 4 yearsIt defeats the purpose of a SecureString because it makes a non-encrypted copy of your SecureString content in to a normal string. Every time you do that, you are adding at least one (and with Garbage Collection possibly multiple) copies of your unencrypted string to memory. This is considered a risk for some security sensitive applications and SecureString was implemented specifically to reduce the risk.
-
Wim Coenen over 4 years@BenVoigt BSTR has a null terminator after the string data for safety, but also allows null characters embedded in the string. So it's a bit more complicated than that, you also need to retrieve the length prefix that sits before that pointer. docs.microsoft.com/en-us/previous-versions/windows/desktop/…
-
Ben Voigt over 4 years@WimCoenen: True but unimportant. The length stored in the BSTR will be a copy of the length already available from
SecureString.Length
. -
Wim Coenen over 4 years@BenVoigt ah, my bad. I thought SecureString didn't expose any information about the string.
-
Ben Voigt over 4 years@WimCoenen:
SecureString
is not trying to hide the value, it's trying to prevent copies of the value from being made into regions which cannot be reliably overwritten, such as garbage collected memory, pagefile, etc. The intention is that when theSecureString
lifetime ends, absolutely no copy of the secret remains in memory. It doesn't prevent you from making and leaking a copy, but it never does. -
Ben Voigt over 4 yearsWhile your code doesn't leak a copy of the string, it still represents a pit of despair. Nearly every operation on the
System.String
object will make unpinned and unerased copies. That's why this isn't built intoSecureString
. -
mklement0 about 4 yearsIt's somewhat of a moot point, given that you're creating a managed copy, but it's generally better to call
Marshal.ZeroFreeBSTR()
, notMarshal.FreeBSTR()
. -
mklement0 about 4 years@sclarke81, good idea, but you'll need to use
[char]
, not[byte]
. -
mklement0 about 4 yearsNice, though to zero out the entire string you'll have to use
new char[length]
(or multiplylength
withsizeof(char)
). -
mklement0 about 4 years@BenVoigt: As long as the
action
delegate doesn't create copies of the temporary, pinned, then zeroed-out string, this approach should be as safe as or unsafe asSecureString
itself - to use the latter, a plain-text representation too has to be created at some point, given that secure strings aren't OS-level constructs; the relative security comes from controlling the lifetime of that string and ensuring that it gets erased after use. -
mklement0 about 4 yearsThe overall approach is promising, but I don't think your attempt at pinning the managed string that contains the insecure (plain-text) copy is effective: what you're pinning instead is the original string object that you've initialized to
String.Empty
, not the newly allocated instance created and returned byMarshal.PtrToStringUni()
. -
mklement0 about 4 yearsGood point; I've left a comment on the referenced answer, which should notify the OP.
-
Ben Voigt about 4 years@mklement0:
SecureString
doesn't have member functions and overloaded operators that make copies all over the place.System.String
does. -
mklement0 about 4 years@BenVoigt, yes, it's very easy to create accidental copies of managed strings; as as long as the
action
delegate doesn't do that - and that's what this hinges on - this approach should be fine; passing the string through as-is to some API that requires a plain-text password fulfills that requirement (leaving aside the issue of how careful that API is in controlling the string). -
Ben Voigt about 4 years@mklement0: But "passing the string through as-is to some API" can be done using the
SecureString
object -- p/invoke knows what to do with it. The problem is in using it like it's an ordinary managed string, because you made it look like one. Basically anything that needs to be done will, by default, get done wrong with this call signature, whether that be converting to an encoding other than UCS-2, trimming or appending data, escaping control characters, or whatever. Even calling a string comparison function could put you at risk if that function computes a normalized version! -
mklement0 about 4 years@BenVoigt, not all APIs support
SecureString
- e.g.,System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials()
- and that's what this approach is for - undoubtedly, you have to be careful. -
Ben Voigt about 4 years@mklement0: Which is pretty darn absurd considering that it passes it to the
NetworkCredential
constructor which DOES accept aSecureString
. -
K. Frank about 4 yearsThis answer does not work on non windows platforms. PtrToStringAuto is wrong for an explanation see: github.com/PowerShell/PowerShell/issues/…
-
brumScouse over 2 yearsI'm not sure if you intended it, but the arguments need to have "this" prefixed to be proper extension methods
-
ryanwebjackson over 2 yearsI believe ToSecureString should only be used for testing as well, because the point of SecureString is to keep the string out of memory.
-
Matt over 2 years@ryanwebjackson - Yes. And additionally this isn't as good as true encryption. There is no key used so everyone can show the plaintext. So it shouldn't be named "secure". Another example of "security through obscurity".
-
Kit Ramos about 2 yearsto me I think this is the best way you don't have to go about defining new functions or extensions or any of that other overhead. especially nice when you only need to do this once and would rather not have to add a ton of code.