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

时间:2010-12-21 20:22:48

标签: c# encryption bytearray rsa securestring

我有一个方法,当前返回从字节数组转换的字符串:

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);
}

我正在尝试更新此方法,而是返回SecureString,但我无法将RSACryptoServiceProvider.Decrypt的返回值从byte[]转换为SecureString。我尝试了以下方法:

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;

但是,使用this SecureString equality tester时,生成的SecureString不等于原始未加密文本构造的SecureString。我的加密和解密方法之前有效,当我在任何地方使用string时,我也测试了SecureString等式代码,所以我很确定这里的问题是我在尝试将byte[]转换为SecureString。我应该采用另一种方法来使用RSA加密,这样我可以在解密时取回SecureString吗?

编辑:我不想将字节数组转换为常规字符串,然后将该字符串填充到SecureString中,因为这似乎打败了the point of using a SecureString第一名。但是,Decrypt返回byte[]是否也很糟糕,然后我尝试将该字节数组填充到SecureString中?我的猜测是,如果Decrypt返回byte[],那么这是传递敏感信息的安全方式,因此将数据的一个安全表示转换为另一个安全表示似乎没问题。

5 个答案:

答案 0 :(得分:3)

char和byte可以与cast一起使用,所以修改你的第二块代码:

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

return secStr;

这应该可以正常工作,但请记住,你仍然将未加密的信息带入内存中的“清晰”信息,因此有一点可以让它受到损害(哪种方式会使{{{ 1}})。

** 更新 **

SecureString您的敏感信息不安全。您可以在内存中查看它并查看信息(特别是如果它只是一个字符串)。单个字节将按照字符串的确切顺序排列,因此“读取”非常简单。

我(实际上大约一个小时前)我自己正在努力解决这个问题,据我所知,除非解密器是专门编程的,否则没有好办法直接从解密器转到byte[]支持这一战略。

答案 1 :(得分:3)

我认为问题可能是您的ByteConvert.GetChars方法。我在MSDN文档中找不到该类或方法。我不确定这是错字还是本土功能。无论如何,它很可能无法正确解释字节的编码。相反,请使用UTF8Encoding's GetChars method。它将正确地将字节转换回.NET字符串,假设它们最初是从.NET字符串对象加密的。 (如果没有,您会希望在encoding上使用与原始字符串匹配的GetChars方法。)

使用数组是最安全的方法。因为您的秘密的解密表示存储在字节或字符数组中,所以您可以在完成后轻松清除它们,因此您的明文秘密不会留在内存中。这不是很安全,但比转换为字符串更安全。字符串无法更改,并且它们会留在内存中,直到它们在未来某个不确定的时间被垃圾收集。

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;

答案 2 :(得分:0)

基于Coding Gorilla's answer,我在Decrypt方法中尝试了以下内容:

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

调试时,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!!!"

所以看起来我可以浏览byte[]数组,直接转换为char,并跳过\0个字符。就像编码大猩猩所说的那样,这似乎再次在某种程度上打败了SecureString,因为敏感数据在内存中以小byte大小的块浮动。有关让RSACryptoServiceProvider.Decrypt直接退回SecureString的任何建议吗?

编辑:是的,这有效:

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

修改:更正:这适用于普通的旧英文字符串。加密然后尝试解密字符串"標準語 明治維新 english やった"不能按预期工作,因为使用此foreach (byte b in decryptedBytes)技术生成的解密字符串与原始未加密字符串不匹配。

编辑:使用以下两种作品:

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

这仍然会在内存中留下一个字节数组和一个密码的char数组,这很糟糕。也许我应该找到另一个返回SecureString的RSA类。 :/

答案 3 :(得分:0)

如果你坚持使用UTF-16怎么办?

在.NET内部(因此,SecureString)使用UTF-16(双字节)来存储字符串内容。您可以利用这一点并一次将受保护的数据转换为两个字节(即1个字符)......

加密时,剥离Char,然后使用Encoding.UTF16.GetBytes()获取两个字节,并将这两个字节压入加密流。相反,当您从加密流中读取时,一次读取两个字节,并使用UTF16.GetString()来获取您的字符。

它可能听起来很糟糕,但是它保留了你的秘密字符串中的所有字符都在一个地方,并且它为你提供了字符的可靠性&#34; size&#34; (你不必猜测下一个单字节是char,还是双宽字符的UTF标记)。观察者无法知道哪些角色与哪个角色有关,也不知道哪个角色,所以猜测这个秘密几乎是不可能的。

老实说,这只是一个建议的想法......我即将自己尝试,看看它有多可行。我的目标是生成扩展方法(SecureString.Encrypt和ICrypto.ToSecureString,或类似的东西)。

答案 4 :(得分:-1)

使用System.Encoding.Default.GetString GetString MSDN