简体   繁体   English

C# - RSACryptoServiceProvider解密为SecureString而不是字节数组

[英]C# - RSACryptoServiceProvider Decrypt into a SecureString instead of byte array

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 . 我正在尝试更新此方法而不是返回SecureString ,但是我无法将RSACryptoServiceProvider.Decrypt的返回值从byte[]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. 但是,使用此SecureString相等性测试程序 ,生成的SecureString不等于从原始的未加密文本构造的SecureString 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 . 我的加密和解密方法以前工作,当我只是在任何地方使用string ,我也测试了SecureString相等代码,所以我很确定这里的问题是我如何尝试将byte[]转换为SecureString Is there another route I should take for using RSA encryption that would allow me to get back a SecureString when I decrypt? 我应该采用另一种方法来使用RSA加密,这样我可以在解密时恢复SecureString吗?

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. 编辑:我不想将字节数组转换为常规字符串,然后将该字符串填充到SecureString ,因为这似乎首先打败了使用SecureString的点 However, is it also bad that Decrypt returns byte[] and I'm then trying to stuff that byte array into a SecureString ? 但是, Decrypt返回byte[]并且然后我尝试将该字节数组填充到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. 我的猜测是,如果Decrypt返回一个byte[] ,那么这是一种传递敏感信息的安全方法,因此将数据的一个安全表示转换为另一个安全表示似乎没问题。

I think the problem might be your ByteConvert.GetChars method. 我认为问题可能是你的ByteConvert.GetChars方法。 I can't find that class or method in the MSDN docs. 我在MSDN文档中找不到该类或方法。 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 . 相反,请使用UTF8Encoding的GetChars方法 It will properly convert the bytes back into a .NET string, assuming they were encrypted from a .NET string object originally. 它将正确地将字节转换回.NET字符串,假设它们最初是从.NET字符串对象加密的。 (If not, you'll want to use the GetChars method on the encoding that matches the original string.) (如果没有,您将需要在与原始字符串匹配的编码上使用GetChars方法。)

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;

A char and a byte can be used interchangeably with casting, so modify your second chunk of code as such: char和byte可以与cast一起使用,因此修改你的第二块代码:

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 ). 这应该可以正常工作,但请记住,您仍然将未加密的信息带入内存中的“清除”,因此有一点可以让它受到损害(哪种类型会破坏SecureString的目的)。

** Update ** ** 更新 **

A byte[] of your sensitive information is not secure. 敏感信息的byte[]不安全。 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. 我(实际上大约一个小时前)我自己正在努力解决同样的问题,据我所知,没有好办法直接从解密器到SecureString除非解密器专门编程支持这种策略。

Based on Coding Gorilla's answer , I tried the following in my Decrypt method: 根据Coding Gorilla的回答 ,我在Decrypt方法中尝试了以下内容:

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: 调试时, decryptedString1decryptedString2不相等:

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. 所以看起来我可以通过byte[]数组,直接转换为char ,并跳过\\0字符。 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. 就像编码Gorilla所说的那样,这似乎再次部分地击败了SecureString ,因为敏感数据在内存中以小byte大小的块浮动。 Any suggestions for getting RSACryptoServiceProvider.Decrypt to return a SecureString directly? 有关获取RSACryptoServiceProvider.Decrypt直接返回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. 编辑:更正:这适用于普通的旧英语字符串。 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. 加密然后尝试解密字符串"標準語 明治維新 english やった"不能按预期工作,因为使用此foreach (byte b in decryptedBytes) "標準語 明治維新 english やった" foreach (byte b in decryptedBytes)技术生成的解密字符串与原始未加密字符串不匹配。

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. 这仍然会在内存中留下一个字节数组和一个密码的char数组,这很糟糕。 Maybe I should find another RSA class that returns a SecureString . 也许我应该找到另一个返回SecureString RSA类。 :/ :/

What if you stuck to UTF-16? 如果你坚持使用UTF-16怎么办?

Internally, .NET (and therefore, SecureString) uses UTF-16 (double byte) to store string contents. 在内部,.NET(以及SecureString)使用UTF-16(双字节)来存储字符串内容。 You could take advantage of this and translate your protected data two bytes (ie 1 char) at a time... 您可以利用这一点并一次将受保护的数据转换为两个字节(即1个字符)...

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. 加密时,剥离Char,然后使用Encoding.UTF16.GetBytes()获取两个字节,并将这两个字节压入加密流。 In reverse, when you are reading from your encrypted stream, read two bytes at a time, and UTF16.GetString() to get your char. 相反,当您从加密流中读取时,一次读取两个字节,并使用UTF16.GetString()来获取您的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). 它可能听起来很糟糕,但它保留了你的秘密字符串中的所有字符都在一个地方,并且它为你提供了字符“大小”的可靠性(你不必猜测下一个字节是否为char,或双宽字符的UTF标记。 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). 我的目标是生成扩展方法(SecureString.Encrypt和ICrypto.ToSecureString,或类似的东西)。

使用System.Encoding.Default.GetString GetString MSDN

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM