C # - RSACryptoServiceProvider Decrypt in SecureString instead of byte array

I have a method that currently returns a string converted from an array of bytes:

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 am trying to update this method, instead I will return it to SecureString , but it is difficult for me to convert 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 security verification tester , the resulting SecureString not equal to the SecureString built from the original, unencrypted text. My encryption and decryption methods worked before, when I just used string everywhere and I also checked the SecureString equality code, so I'm sure the problem is how I try to convert byte[] to SecureString . Is there another route I should use to use RSA encryption that would allow me to return a SecureString when decrypting?

Edit: I did not want to convert the byte array to a regular string, and then paste this string into a SecureString , because this seems to defeat the SecureString usage point in the first place. However, it is also bad that Decrypt returns byte[] , and then I try to write this byte array to SecureString ? I believe that if Decrypt returns byte[] , then this is a safe way to transfer sensitive information, so converting one secure presentation of data to another secure presentation looks fine.

+4
source share
5 answers

Based on the Gorilla response Encoding , I tried the following in the 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, make a direct tide to char and skip the \0 characters. As was the case with encoding, Gorilla said that it seemed to again partially lose the SecureString point, because sensitive data was dropped in memory in small byte fragments. Any suggestions for getting RSACryptoServiceProvider.Decrypt for direct return of SecureString ?

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. The encryption and attempt to decrypt the string "標準θͺž ζ˜Žζ²»ηΆ­ζ–° english γ‚„γ£γŸ" does not work as expected, because the received decrypted string using this foreach (byte b in decryptedBytes) method foreach (byte b in decryptedBytes) 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 an array of bytes and an array of char passwords in memory, which sucks. Maybe I should find another RSA class that returns SecureString .: /

+1
source

A char, and the byte can be used interchangeably with casting, so change the second piece of code as such:

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

This should work as expected, but keep in mind that you are still adding unencrypted information to the β€œclear” memory, so the dot may be compromised there (some kind of target hit in SecureString ).

** Update **

A byte[] 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 the "read" is pretty straight forward.

I was (actually about an hour ago) just struggling with this very problem myself, and as far as I know, there is no good way to go directly from decrypter to SecureString if the decompiler is not specifically programmed to support this strategy.

+3
source

I think the problem may be your ByteConvert.GetChars method. I cannot find this class or method in MSDN docs. I'm not sure if this is a typo or a homegrown feature. Despite this, most likely, it will not correctly interpret the byte encoding. Instead, use the UTF8Encoding GetChars method. It will correctly convert bytes back to a .NET string, assuming they were encrypted from a .NET string object initially. (If not, you'll want to use the GetChars method on encoding , which matches the original string.)

You are right that using arrays is the safest approach. Since the decrypted representations of your cache are stored in bytes or char arrays, you can easily clear them when this is done, so the secret of unencrypted text will not remain in memory. This is not entirely safe, but more secure than converting to a string. Lines cannot be changed, and they remain in memory until they are collected in a certain indefinite period of 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; 
+3
source

Use System.Encoding.Default.GetString GetString MSDN

0
source

What to do if you stick to UTF-16?

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

When you are encrypting, cancel the Char and use Encoding.UTF16.GetBytes () to get two bytes and insert these two bytes into your encryption stream. In reverse, when you read from your encrypted stream, read two bytes at a time, and UTF16.GetString () to get a char.

It probably sounds awful, but in it all the characters of your secret line are in one place, and this gives you the reliability of the "size" of the character (you do not have to wonder if the next single byte is a Char or a UTF token for double width char). There is no way for the observer to know which characters are coming with which and in what order, so guessing the secret should be almost impossible.

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

0
source

Source: https://habr.com/ru/post/1332957/


All Articles