В C# - rsacryptoserviceprovider будет расшифровать в объект 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;

Однако, используяэтот тестер равенства SecureString , полученный SecureString не был равен SecureString, построенному из оригинального, незашифрованного текста. Мои методы шифрования и дешифрования работали и раньше, когда я просто использовал string везде, и я также протестировал код равенства SecureString, поэтому я уверен, что проблема здесь заключается в том, как я пытаюсь преобразовать byte[] в SecureString. Существует ли другой маршрут, который я должен использовать для шифрования RSA, который позволит мне получить обратно SecureString при расшифровке?

Edit: я не хотел преобразовывать массив байтов в обычную строку, а затем набивать эту строку в SecureString, потому что это, кажется, побеждает смысл использования SecureString В городе первое место. Однако плохо ли также, что Decrypt возвращает byte[] , и я затем пытаюсь запихнуть этот массив байтов в SecureString? Я думаю, что если Decrypt возвращает byte[], то это безопасный способ передачи конфиденциальной информации, поэтому преобразование одного безопасного представления данных в другое безопасное представление кажется нормальным.

5 4

5 ответов:

Символ и Байт могут использоваться взаимозаменяемо с приведением, поэтому измените свой второй фрагмент кода следующим образом:

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

return secStr;
Это должно работать должным образом, но имейте в виду, что вы все еще приносите незашифрованную информацию в "чистую" память, поэтому есть точка, в которой она может быть скомпрометирована (что в некотором роде нарушает цель SecureString).

** обновление **

A byte[] вашей конфиденциальной информации не является безопасным. Вы можете посмотреть на него в памяти и увидеть информация (особенно если это просто строка). Отдельные байты будут находиться в точном порядке строки, поэтому "читать" ее довольно прямолинейно.

Я сам (на самом деле около часа назад) просто боролся с этим же вопросом, и, насколько я знаю, нет хорошего способа перейти прямо от дешифратора к SecureString, если только дешифратор не запрограммирован специально для поддержки этой стратегии.

Я думаю, что проблема может быть в вашем методе ByteConvert.GetChars. Я не могу найти этот класс или метод в документах MSDN. Я не уверен, является ли это опечаткой или доморощенной функцией. Несмотря на это, он, скорее всего, не интерпретирует кодировку байтов правильно. Вместо этого используйте метод GetChars UTF8Encoding. Он правильно преобразует байты обратно в строку .NET, предполагая, что они были зашифрованы из объекта строки .NET изначально. (Если нет, то вы захотите использовать метод 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;

Основываясь на кодировании ответа гориллы , я попробовал следующий метод Decrypt:

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

При отладке decryptedString1 и decryptedString2 не были равны:

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 для прямого возврата A SecureString?

Edit: Да, это работает:

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

Edit: исправление: это работает с простыми старыми английскими строками. Шифрование и последующая попытка расшифровать строку "標準語 明治維新 english やった" не работает должным образом, потому что полученная расшифрованная строка, используя этот метод foreach (byte b in decryptedBytes), не соответствует исходной незашифрованной строке.

Edit: используя следующие работы для обоих:

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

Это все еще оставляет a массив байтов и массив символов пароля в памяти, который отстой. Может быть, мне следует найти другой класс RSA, который возвращает A SecureString. : /

Использовать Систему.Кодирование.По умолчанию.Метода getString GetString MSDN

Что делать, если вы придерживаетесь UTF-16?

Внутри .NET (и, следовательно, SecureString) использует UTF-16 (двойной байт) для хранения содержимого строки. Вы можете воспользоваться этим и перевести ваши защищенные данные на два байта (т. е. 1 символ) за один раз...

Когда вы шифруете, снимите символ и используйте кодировку.UTF16.GetBytes (), чтобы получить ваши два байта,и вставьте эти два байта в поток шифрования. В обратном случае, когда вы читаете из зашифрованного потока, считывайте по два байта за раз, и UTF16.GetString (), чтобы получить свой символ.

Это, вероятно, звучит ужасно, но это удерживает все символы вашей секретной строки от того, чтобы быть все в одном месте, и это дает вам надежность символа "size" (вам не придется угадывать, является ли следующий одиночный байт символом или маркером UTF для двойного символа). Наблюдатель не может знать, какие символы идут рядом с какими и в каком порядке, поэтому угадать секрет почти невозможно.

Честно говоря, это просто предположение идея... Я собираюсь попробовать его сам и посмотреть, насколько он жизнеспособен. Моя цель-создать методы расширения (SecureString.Шифрование и ICrypto.ToSecureString, или что-то в этом роде).