5

I have a method that currently returns a string converted from a byte array:

public static readonly UnicodeEncoding ByteConverter = new UnicodeEncoding();
public static string Decrypt(string textToDecrypt, string privateKeyXml)
{
    if (string.IsNullOrEmpty(textToDecrypt))
    {
        throw new ArgumentException(
            "Cannot decrypt null or blank string"
        );
    }
    if (string.IsNullOrEmpty(privateKeyXml))
    {
        throw new ArgumentException("Invalid private key XML given");
    }
    byte[] bytesToDecrypt = Convert.FromBase64String(textToDecrypt);
    byte[] decryptedBytes;
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.FromXmlString(privateKeyXml);
        decryptedBytes = rsa.Decrypt(bytesToDecrypt, FOAEP);
    }
    return ByteConverter.GetString(decryptedBytes);
}

I'm trying to update this method to instead return a SecureString, but I'm having trouble converting the return value of RSACryptoServiceProvider.Decrypt from byte[] to SecureString. I tried the following:

var secStr = new SecureString();
foreach (byte b in decryptedBytes)
{
    char[] chars = ByteConverter.GetChars(new[] { b });
    if (chars.Length != 1)
    {
        throw new Exception(
            "Could not convert a single byte into a single char"
        );
    }
    secStr.AppendChar(chars[0]);
}
return secStr;

However, using this SecureString equality tester, the resulting SecureString was not equal to the SecureString constructed from the original, unencrypted text. My Encrypt and Decrypt methods worked before, when I was just using string everywhere, and I've also tested the SecureString equality code, so I'm pretty sure the problem here is how I'm trying to convert byte[] into SecureString. Is there another route I should take for using RSA encryption that would allow me to get back a SecureString when I decrypt?

Edit: I didn't want to convert the byte array to a regular string and then stuff that string into a SecureString, because that seems to defeat the point of using a SecureString in the first place. However, is it also bad that Decrypt returns byte[] and I'm then trying to stuff that byte array into a SecureString? It's my guess that if Decrypt returns a byte[], then that's a safe way to pass around sensitive information, so converting one secure representation of the data to another secure representation seems okay.

Community
  • 1
  • 1
Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222
  • 2
    You might find this article helpful: http://www.codeproject.com/KB/security/SymmetricAlgorithmHelper.aspx?PageFlow=Fluid It does decrypt the data to a byte array and converts it to a SecureString. Only other slight improvement I could think of is to put your Decrypted output in a foreach loop and not store it in a byte array. – SwDevMan81 Dec 21 '10 at 21:24
  • Hm, that link has code that explicitly stores data in an array so that they can flush the array later: `Array.Clear(utf8Buffer, 0, utf8Buffer.Length);`. Can someone chime in on which is better? Maybe I should post another question. :/ – Sarah Vessels Dec 21 '10 at 21:42
  • The strings should be the same. How are you encrypting? Using the same value for the FOAEP value? Can you give an example of the string that isn't decrypting correctly? – Aaron Jensen Jul 10 '15 at 21:05

5 Answers5

3

I think the problem might be your ByteConvert.GetChars method. I can't find that class or method in the MSDN docs. I'm not sure if that is a typo, or a homegrown function. Regardless, it is mostly likely not interpreting the encoding of the bytes correctly. Instead, use the UTF8Encoding's GetChars method. It will properly convert the bytes back into a .NET string, assuming they were encrypted from a .NET string object originally. (If not, you'll want to use the GetChars method on the encoding that matches the original string.)

You're right that using arrays is the most secure approach. Because the decrypted representations of your secret are stored in byte or char arrays, you can easily clear them out when done, so your plaintext secret isn't left in memory. This isn't perfectly secure, but more secure than converting to a string. Strings can't be changed and they stay in memory until they are garbage collected at some indeterminate future time.

var secStr = new SecureString();
var chars = System.Text.Encoding.UTF8.GetChars(decryptedBytes);
for( int idx = 0; idx < chars.Length; ++idx )
{
    secStr.AppendChar(chars[idx]);
    # Clear out the chars as you go.
    chars[idx] = 0
}

# Clear the decrypted bytes from memory, too.
Array.Clear(decryptedBytes, 0, decryptedBytes.Length);

return secStr;
Aaron Jensen
  • 25,861
  • 15
  • 82
  • 91
3

A char and a byte can be used interchangeably with casting, so modify your second chunk of code as such:

var secStr = new SecureString();
foreach (byte b in decryptedBytes)
{
   secStr.AppendChar((char)b);
}

return secStr;

This should work properly, but keep in mind that you're still bringing the unencrypted information into the "clear" in memory, so there's a point at which it could be compromised (which sort of defeats the purpose to a SecureString).

** Update **

A byte[] of your sensitive information is not secure. You can look at it in memory and see the information (especially if it's just a string). The individual bytes will be in the exact order of the string, so 'read'ing it is pretty straight-forward.

I was (actually about an hour ago) just struggling with this same issue myself, and as far as I know there is no good way to go straight from the decrypter to the SecureString unless the decryter is specifically programmed to support this strategy.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
  • I tried this initially, before using the `ByteConverter`. My `SecureString`s still weren't equal. You just prompted me to test something else, though, so let me post a new answer--I think I'm on to something! – Sarah Vessels Dec 21 '10 at 20:38
  • I wonder if there's a way to force the `byte[]` to be wiped from memory after its contents have been read into a `SecureString`? – Sarah Vessels Dec 21 '10 at 20:47
  • Sure you could easily just do a `for(int i=0; i < decryptedBytes.Length; i++) { decryptedBytes[i] = 0; }`. But all I have to do (as ye ole' hacker) is break the code before you wipe it, and I've got you. – CodingGorilla Dec 21 '10 at 20:51
  • SwDevMan81 linked this in a comment above: http://www.codeproject.com/KB/security/SymmetricAlgorithmHelper.aspx?PageFlow=Fluid It stores data in arrays specifically so it can flush them later with `Array.Clear(utf8Buffer, 0, utf8Buffer.Length);`. Is that effective, I wonder? – Sarah Vessels Dec 21 '10 at 21:43
0

Based on Coding Gorilla's answer, I tried the following in my Decrypt method:

string decryptedString1 = string.Empty;
foreach (byte b in decryptedBytes)
{
    decryptedString1 += (char)b;
}
string decryptedString2 = ByteConverter.GetString(decryptedBytes);

When debugging, decryptedString1 and decryptedString2 were not equal:

decryptedString1    "m\0y\0V\0e\0r\0y\0L\0o\0n\0g\0V\03\0r\0y\05\03\0c\0r\03\07\0p\04\0s\0s\0w\00\0r\0d\0!\0!\0!\0"
decryptedString2    "myVeryLongV3ry53cr37p4ssw0rd!!!"

So it looks like I can just go through the byte[] array, do a direct cast to char, and skip \0 characters. Like Coding Gorilla said, though, this does seem to again in part defeat the point of SecureString, because the sensitive data is floating about in memory in little byte-size chunks. Any suggestions for getting RSACryptoServiceProvider.Decrypt to return a SecureString directly?

Edit: yep, this works:

var secStr = new SecureString();
foreach (byte b in decryptedBytes)
{
    var c = (char)b;
    if ('\0' == c)
    {
        continue;
    }
    secStr.AppendChar(c);
}
return secStr;

Edit: correction: this works with plain old English strings. Encrypting and then attempting to decrypt the string "標準語 明治維新 english やった" doesn't work as expected because the resulting decrypted string, using this foreach (byte b in decryptedBytes) technique, does not match the original unencrypted string.

Edit: using the following works for both:

var secStr = new SecureString();
foreach (char c in ByteConverter.GetChars(decryptedBytes))
{
    secStr.AppendChar(c);
}
return secStr;

This still leaves a byte array and a char array of the password in memory, which sucks. Maybe I should find another RSA class that returns a SecureString. :/

Community
  • 1
  • 1
Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222
  • If you come up with a way to skip the 'insecure' intermediate step, I'd love to hear it. =) – CodingGorilla Dec 21 '10 at 20:48
  • BTW, if, before encrypting your string, you use the ASCIIEncoder on the string, it will get rid of your `\0`'s. Those are an artifact of Unicode and MBCS (that is of course assuming you don't need Unicode/MBCS support). – CodingGorilla Dec 21 '10 at 20:50
  • Write a new `RSACryptoServiceProvider`-type class whose `Decrypt` and `Encrypt` return/use `SecureString` instead of byte arrays? :P – Sarah Vessels Dec 21 '10 at 20:51
  • I know that I am late to the party on this one, but after you append the character to the string you can set it's value to \0. Since you are actually dealing with a array of bytes this will wipe out the string as you consume it. I don't think that there is a way to get around having the byte array of plain text, but you can minimize the amount of time it actually exists. – Joseph Carrigan Oct 10 '12 at 03:28
0

What if you stuck to UTF-16?

Internally, .NET (and therefore, SecureString) uses UTF-16 (double byte) to store string contents. You could take advantage of this and translate your protected data two bytes (i.e. 1 char) at a time...

When you encrypt, peel off a Char, and use Encoding.UTF16.GetBytes() to get your two bytes, and push those two bytes into your encryption stream. In reverse, when you are reading from your encrypted stream, read two bytes at a time, and UTF16.GetString() to get your char.

It probably sounds awful, but it keeps all the characters of your secret string from being all in one place, AND it gives you the reliability of character "size" (you won't have to guess if the next single byte is a char, or a UTF marker for a double-wide char). There's no way for an observer to know which characters go with which, nor in which order, so guessing the secret should be near impossible.

Honestly, this is just a suggested idea... I'm about to try it myself, and see how viable it is. My goal is to produce extension methods (SecureString.Encrypt and ICrypto.ToSecureString, or something like that).

Craig Brunetti
  • 555
  • 5
  • 6
-1

Use System.Encoding.Default.GetString GetString MSDN

Gary L Cox Jr
  • 680
  • 3
  • 10
  • I was trying to avoid having the decrypted text in a regular string, otherwise what's the point of using `SecureString` elsewhere if the sensitive data is already floating around memory in a regular string? At least from my understanding of things. See http://stackoverflow.com/questions/141203/when-would-i-need-a-securestring-in-net – Sarah Vessels Dec 21 '10 at 20:29
  • I see, I saw that your method was Decrypt so I assumed you wanted the decrypted value, you could return it as a Base64 using Convert.ToBase64String(thebytes); then maybe use that to pass into your SecureString? – Gary L Cox Jr Dec 21 '10 at 20:31